From e3c592e96a5658b301f7f94b927c78c3879266d9 Mon Sep 17 00:00:00 2001 From: Sylvain Garancher Date: Tue, 4 Apr 2017 19:23:07 +0200 Subject: [PATCH 01/46] [ADD] Add printer_zpl2 module (#66) * [FIX] printer_tray: Allow to call print_option with no report * [ADD] Add printer_zpl2 module --- printer_zpl2/README.rst | 99 ++ printer_zpl2/__init__.py | 6 + printer_zpl2/__openerp__.py | 24 + printer_zpl2/models/__init__.py | 6 + printer_zpl2/models/printing_label_zpl2.py | 188 ++++ .../models/printing_label_zpl2_component.py | 144 +++ printer_zpl2/security/ir.model.access.csv | 5 + printer_zpl2/tests/__init__.py | 6 + .../tests/test_printing_label_zpl2.py | 941 ++++++++++++++++++ .../tests/test_wizard_print_record_label.py | 92 ++ printer_zpl2/views/printing_label_zpl2.xml | 144 +++ printer_zpl2/wizard/__init__.py | 5 + printer_zpl2/wizard/print_record_label.py | 44 + printer_zpl2/wizard/print_record_label.xml | 30 + 14 files changed, 1734 insertions(+) create mode 100644 printer_zpl2/README.rst create mode 100644 printer_zpl2/__init__.py create mode 100644 printer_zpl2/__openerp__.py create mode 100644 printer_zpl2/models/__init__.py create mode 100644 printer_zpl2/models/printing_label_zpl2.py create mode 100644 printer_zpl2/models/printing_label_zpl2_component.py create mode 100644 printer_zpl2/security/ir.model.access.csv create mode 100644 printer_zpl2/tests/__init__.py create mode 100644 printer_zpl2/tests/test_printing_label_zpl2.py create mode 100644 printer_zpl2/tests/test_wizard_print_record_label.py create mode 100644 printer_zpl2/views/printing_label_zpl2.xml create mode 100644 printer_zpl2/wizard/__init__.py create mode 100644 printer_zpl2/wizard/print_record_label.py create mode 100644 printer_zpl2/wizard/print_record_label.xml diff --git a/printer_zpl2/README.rst b/printer_zpl2/README.rst new file mode 100644 index 0000000..6336639 --- /dev/null +++ b/printer_zpl2/README.rst @@ -0,0 +1,99 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +===================== +ZPL II Label printing +===================== + +This module extends the **Report to printer** (``base_report_to_printer``) +module to add a ZPL II label printing feature. + +This module is meant to be used as a base for module development, and does not provide a GUI on its own. +See below for more details. + +Installation +============ + +Nothing special, just install the module. + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Settings > Printing > Labels > ZPL II* +#. Create new labels + +It's also possible to add a label printing wizard on any model by creating a new *ir.values* record. +For example, to add the printing wizard on the *product.product* model : + +.. code-block:: xml + + + Print Product Label + action + client_action_multi + product.product + + + +Usage +===== + +To print a label, you need to call use the label printing method from anywhere (other modules, server actions, etc.). + +.. code-block:: python + + # Example : Print the label of a product + self.env['printing.label.zpl2'].browse(label_id).print_label( + self.env['printing.printer'].browse(printer_id), + self.env['product.product'].browse(product_id)) + +You can also use the generic label printing wizard, if added on some models. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/144/9.0 + +Known issues / Roadmap +====================== + +* Add a button to generate the ir.values for a model +* Develop a "Designer" view in a separate module, to allow drawing labels with simple mouse clicks/drags + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Sylvain Garancher + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/printer_zpl2/__init__.py b/printer_zpl2/__init__.py new file mode 100644 index 0000000..6b40cb0 --- /dev/null +++ b/printer_zpl2/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/printer_zpl2/__openerp__.py b/printer_zpl2/__openerp__.py new file mode 100644 index 0000000..1e1f614 --- /dev/null +++ b/printer_zpl2/__openerp__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Printer ZPL II', + 'version': '9.0.1.0.0', + 'category': 'Printer', + 'author': 'SYLEAM, Odoo Community Association (OCA)', + 'website': 'http://www.syleam.fr/', + 'license': 'AGPL-3', + 'external_dependancies': { + 'python': ['zpl2'], + }, + 'depends': [ + 'base_report_to_printer', + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/printing_label_zpl2.xml', + 'wizard/print_record_label.xml', + ], + 'installable': True, +} diff --git a/printer_zpl2/models/__init__.py b/printer_zpl2/models/__init__.py new file mode 100644 index 0000000..048ad6e --- /dev/null +++ b/printer_zpl2/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import printing_label_zpl2_component +from . import printing_label_zpl2 diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py new file mode 100644 index 0000000..b93e7c1 --- /dev/null +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import time +import datetime +import logging +from openerp import api, exceptions, fields, models +from openerp.tools.translate import _ +from openerp.tools.safe_eval import safe_eval + +_logger = logging.getLogger(__name__) + +try: + import zpl2 +except ImportError: + _logger.debug('Cannot `import zpl2`.') + + +class PrintingLabelZpl2(models.Model): + _name = 'printing.label.zpl2' + _description = 'ZPL II Label' + + name = fields.Char(required=True, help='Label Name.') + description = fields.Char(help='Long description for this label.') + model_id = fields.Many2one( + comodel_name='ir.model', string='Model', required=True, + help='Model used to print this label.') + origin_x = fields.Integer( + required=True, default=10, + help='Origin point of the contents in the label, X coordinate.') + origin_y = fields.Integer( + required=True, default=10, + help='Origin point of the contents in the label, Y coordinate.') + width = fields.Integer( + required=True, default=480, + help='With of the label, will be set on the printer before printing.') + component_ids = fields.One2many( + comodel_name='printing.label.zpl2.component', inverse_name='label_id', + string='Label Components', + help='Components which will be printed on the label.') + + @api.multi + def _generate_zpl2_components_data( + self, label_data, record, page_number=1, page_count=1, + label_offset_x=0, 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 = [] + for component in self.component_ids: + eval_args = extra + eval_args.update({ + 'object': record, + 'page_number': str(page_number + 1), + 'page_count': str(page_count), + 'time': time, + 'datetime': datetime, + }) + data = safe_eval(component.data, eval_args) or '' + + # Generate a list of elements if the component is repeatable + for idx in range( + component.repeat_offset, + component.repeat_offset + component.repeat_count): + printed_data = data + # Pick the right value if data is a collection + if isinstance(data, (list, tuple, set, models.BaseModel)): + # If we reached the end of data, quit the loop + if idx >= len(data): + break + + # Set the real data to display + printed_data = data[idx] + + position = idx - component.repeat_offset + to_print.append(( + component, printed_data, + label_offset_x + component.repeat_offset_x * position, + label_offset_y + component.repeat_offset_y * position, + )) + + for (component, data, offset_x, offset_y) in to_print: + component_offset_x = component.origin_x + offset_x + component_offset_y = component.origin_y + offset_y + if component.component_type == 'text': + barcode_arguments = dict([ + (field_name, component[field_name]) + for field_name in [ + zpl2.ARG_FONT, + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_WIDTH, + zpl2.ARG_REVERSE_PRINT, + zpl2.ARG_IN_BLOCK, + zpl2.ARG_BLOCK_WIDTH, + zpl2.ARG_BLOCK_LINES, + zpl2.ARG_BLOCK_SPACES, + zpl2.ARG_BLOCK_JUSTIFY, + zpl2.ARG_BLOCK_LEFT_MARGIN, + ] + ]) + label_data.font_data( + component_offset_x, component_offset_y, + barcode_arguments, data) + elif component.component_type == 'rectangle': + label_data.graphic_box( + component_offset_x, component_offset_y, { + zpl2.ARG_WIDTH: component.width, + zpl2.ARG_HEIGHT: component.height, + zpl2.ARG_THICKNESS: component.thickness, + zpl2.ARG_COLOR: component.color, + zpl2.ARG_ROUNDING: component.rounding, + }) + elif component.component_type == 'circle': + label_data.graphic_circle( + component_offset_x, component_offset_y, { + zpl2.ARG_DIAMETER: component.width, + zpl2.ARG_THICKNESS: component.thickness, + zpl2.ARG_COLOR: component.color, + }) + elif component.component_type == 'sublabel': + component_offset_x += component.sublabel_id.origin_x + component_offset_y += component.sublabel_id.origin_y + component.sublabel_id._generate_zpl2_components_data( + label_data, data, + label_offset_x=component_offset_x, + label_offset_y=component_offset_y) + else: + barcode_arguments = dict([ + (field_name, component[field_name]) + for field_name in [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_CHECK_DIGITS, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + zpl2.ARG_SECURITY_LEVEL, + zpl2.ARG_COLUMNS_COUNT, + zpl2.ARG_ROWS_COUNT, + zpl2.ARG_TRUNCATE, + zpl2.ARG_MODULE_WIDTH, + zpl2.ARG_BAR_WIDTH_RATIO, + ] + ]) + label_data.barcode_data( + component.origin_x + offset_x, + component.origin_y + offset_y, + component.component_type, barcode_arguments, data) + + @api.multi + def _generate_zpl2_data(self, record, page_count=1, **extra): + self.ensure_one() + label_data = zpl2.Zpl2() + + for page_number in range(page_count): + # Initialize printer's configuration + label_data.label_start() + label_data.print_width(self.width) + label_data.label_encoding() + + label_data.label_home(self.origin_x, self.origin_y) + + self._generate_zpl2_components_data( + label_data, record, page_number=page_number, + page_count=page_count) + + # Restore printer's configuration and end the label + label_data.configuration_update(zpl2.CONF_RECALL_LAST_SAVED) + label_data.label_end() + + return label_data.output() + + @api.multi + 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)) + + # Send the label to printer + label_contents = label._generate_zpl2_data( + record, page_count=page_count, **extra) + printer.print_document(None, label_contents, 'raw') + + return True diff --git a/printer_zpl2/models/printing_label_zpl2_component.py b/printer_zpl2/models/printing_label_zpl2_component.py new file mode 100644 index 0000000..c61fd41 --- /dev/null +++ b/printer_zpl2/models/printing_label_zpl2_component.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from openerp import fields, models + +_logger = logging.getLogger(__name__) + +try: + import zpl2 +except ImportError: + _logger.debug('Cannot `import zpl2`.') + + +class PrintingLabelZpl2Component(models.Model): + _name = 'printing.label.zpl2.component' + _description = 'ZPL II Label Component' + _order = 'sequence' + + label_id = fields.Many2one( + comodel_name='printing.label.zpl2', string='Label', + required=True, ondelete='cascade', help='Label using this component.') + sequence = fields.Integer(help='Order used to print the elements.') + name = fields.Char(required=True, help='Name of the component.') + origin_x = fields.Integer( + required=True, default=10, + help='Origin point of the component in the label, X coordinate.') + origin_y = fields.Integer( + required=True, default=10, + help='Origin point of the component in the label, Y coordinate.') + component_type = fields.Selection( + selection=[ + ('text', 'Text'), + ('rectangle', 'Rectangle / Line'), + ('circle', 'Circle'), + (zpl2.BARCODE_CODE_11, 'Code 11'), + (zpl2.BARCODE_INTERLEAVED_2_OF_5, 'Interleaved 2 of 5'), + (zpl2.BARCODE_CODE_39, 'Code 39'), + (zpl2.BARCODE_CODE_49, 'Code 49'), + (zpl2.BARCODE_PDF417, 'PDF417'), + (zpl2.BARCODE_EAN_8, 'EAN-8'), + (zpl2.BARCODE_UPC_E, 'UPC-E'), + (zpl2.BARCODE_CODE_128, 'Code 128'), + (zpl2.BARCODE_EAN_13, 'EAN-13'), + ('sublabel', 'Sublabel'), + ], string='Type', required=True, default='text', oldname='type', + help='Type of content, simple text or barcode.') + font = fields.Selection( + selection=[ + (zpl2.FONT_DEFAULT, 'Default'), + (zpl2.FONT_9X5, '9x5'), + (zpl2.FONT_11X7, '11x7'), + (zpl2.FONT_18X10, '18x10'), + (zpl2.FONT_28X15, '28x15'), + (zpl2.FONT_26X13, '26x13'), + (zpl2.FONT_60X40, '60x40'), + (zpl2.FONT_21X13, '21x13'), + ], required=True, default=zpl2.FONT_DEFAULT, + help='Font to use, for text only.') + thickness = fields.Integer(help='Thickness of the line to draw.') + color = fields.Selection( + selection=[ + (zpl2.COLOR_BLACK, 'Black'), + (zpl2.COLOR_WHITE, 'White'), + ], default=zpl2.COLOR_BLACK, + help='Color of the line to draw.') + orientation = fields.Selection( + selection=[ + (zpl2.ORIENTATION_NORMAL, 'Normal'), + (zpl2.ORIENTATION_ROTATED, 'Rotated'), + (zpl2.ORIENTATION_INVERTED, 'Inverted'), + (zpl2.ORIENTATION_BOTTOM_UP, 'Read from Bottom up'), + ], required=True, default=zpl2.ORIENTATION_NORMAL, + help='Orientation of the barcode.') + check_digits = fields.Boolean( + help='Check if you want to compute and print the check digit.') + height = fields.Integer( + help='Height of the printed component. For a text component, height ' + 'of a single character.') + width = fields.Integer( + help='Width of the printed component. For a text component, width of ' + 'a single character.') + rounding = fields.Integer( + help='Rounding of the printed rectangle corners.') + interpretation_line = fields.Boolean( + help='Check if you want the interpretation line to be printed.') + interpretation_line_above = fields.Boolean( + help='Check if you want the interpretation line to be printed above ' + 'the barcode.') + module_width = fields.Integer( + default=2, help='Module width for the barcode.') + bar_width_ratio = fields.Float( + default=3.0, help='Ratio between wide bar and narrow bar.') + security_level = fields.Integer(help='Security level for error detection.') + columns_count = fields.Integer(help='Number of data columns to encode.') + rows_count = fields.Integer(help='Number of rows to encode.') + truncate = fields.Boolean( + help='Check if you want to truncate the barcode.') + data = fields.Char( + size=256, default='""', required=True, + help='Data to print on this component. Resource values can be ' + 'inserted with %(object.field_name)s.') + sublabel_id = fields.Many2one( + comodel_name='printing.label.zpl2', string='Sublabel', + help='Another label to include into this one as a component. ' + 'This allows to define reusable labels parts.') + repeat = fields.Boolean( + string='Repeatable', + help='Check this box to repeat this component on the label.') + repeat_offset = fields.Integer( + default=0, + help='Number of elements to skip when reading a list of elements.') + repeat_count = fields.Integer( + default=1, + help='Maximum count of repeats of the component.') + repeat_offset_x = fields.Integer( + help='X coordinate offset between each occurence of this component on ' + 'the label.') + repeat_offset_y = fields.Integer( + help='Y coordinate offset between each occurence of this component on ' + 'the label.') + reverse_print = fields.Boolean( + help='If checked, the data will be printed in the inverse color of ' + 'the background.') + in_block = fields.Boolean( + help='If checked, the data will be restrected in a ' + 'defined block on the label.') + block_width = fields.Integer(help='Width of the block.') + block_lines = fields.Integer( + default=1, help='Maximum number of lines to print in the block.') + block_spaces = fields.Integer( + help='Number of spaces added between lines in the block.') + block_justify = fields.Selection( + selection=[ + (zpl2.JUSTIFY_LEFT, 'Left'), + (zpl2.JUSTIFY_CENTER, 'Center'), + (zpl2.JUSTIFY_JUSTIFIED, 'Justified'), + (zpl2.JUSTIFY_RIGHT, 'Right'), + ], string='Justify', required=True, default='L', + help='Choose how the text will be justified in the block.') + block_left_margin = fields.Integer( + string='Left Margin', + help='Left margin for the second and other lines in the block.') diff --git a/printer_zpl2/security/ir.model.access.csv b/printer_zpl2/security/ir.model.access.csv new file mode 100644 index 0000000..acd4271 --- /dev/null +++ b/printer_zpl2/security/ir.model.access.csv @@ -0,0 +1,5 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"printing_label_zpl2_user","Printing Label ZPL2 User","model_printing_label_zpl2","base_report_to_printer.printing_group_user",1,0,0,0 +"printing_label_zpl2_manager","Printing Label ZPL2 Manager","model_printing_label_zpl2","base_report_to_printer.printing_group_manager",1,1,1,1 +"printing_label_zpl2_component_user","Printing Label ZPL2 Component User","model_printing_label_zpl2_component","base_report_to_printer.printing_group_user",1,0,0,0 +"printing_label_zpl2_component_manager","Printing Label ZPL2 Component Manager","model_printing_label_zpl2_component","base_report_to_printer.printing_group_manager",1,1,1,1 diff --git a/printer_zpl2/tests/__init__.py b/printer_zpl2/tests/__init__.py new file mode 100644 index 0000000..4483773 --- /dev/null +++ b/printer_zpl2/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_printing_label_zpl2 +from . import test_wizard_print_record_label diff --git a/printer_zpl2/tests/test_printing_label_zpl2.py b/printer_zpl2/tests/test_printing_label_zpl2.py new file mode 100644 index 0000000..e90d5fe --- /dev/null +++ b/printer_zpl2/tests/test_printing_label_zpl2.py @@ -0,0 +1,941 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import mock + +from openerp import exceptions +from openerp.tests.common import TransactionCase + + +model = 'openerp.addons.base_report_to_printer.models.printing_server' + + +class TestPrintingLabelZpl2(TransactionCase): + def setUp(self): + super(TestPrintingLabelZpl2, self).setUp() + self.Model = self.env['printing.label.zpl2'] + self.ComponentModel = self.env['printing.label.zpl2.component'] + self.server = self.env['printing.server'].create({}) + self.printer = self.env['printing.printer'].create({ + 'name': 'Printer', + 'server_id': self.server.id, + 'system_name': 'Sys Name', + 'default': True, + 'status': 'unknown', + 'status_message': 'Msg', + 'model': 'res.users', + 'location': 'Location', + 'uri': 'URI', + }) + self.label_vals = { + 'name': 'ZPL II Label', + 'model_id': self.env.ref( + 'base_report_to_printer.model_printing_printer').id, + } + self.component_vals = { + 'name': 'ZPL II Label Component', + } + + def new_label(self, vals=None): + values = self.label_vals.copy() + if vals is not None: + values.update(vals) + return self.Model.create(values) + + def new_component(self, vals=None): + values = self.component_vals.copy() + if vals is not None: + values.update(vals) + return self.ComponentModel.create(values) + + def test_print_on_bad_model(self): + """ Check that printing on the bad model raises an exception """ + label = self.new_label() + with self.assertRaises(exceptions.UserError): + label.print_label(self.printer, label) + + @mock.patch('%s.cups' % model) + def test_print_empty_label(self, cups): + """ Check that printing an empty label works """ + label = self.new_label() + label.print_label(self.printer, self.printer) + cups.Connection().printFile.assert_called_once() + + def test_empty_label_contents(self): + """ Check contents of an empty label """ + label = self.new_label() + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ') + + def test_sublabel_label_contents(self): + """ Check contents of a sublabel label component """ + sublabel = self.new_label({ + 'name': 'Sublabel', + }) + data = 'Some text' + self.new_component({ + 'label_id': sublabel.id, + 'data': '"' + data + '"', + }) + label = self.new_label() + self.new_component({ + 'label_id': label.id, + 'name': 'Sublabel contents', + 'component_type': 'sublabel', + 'sublabel_id': sublabel.id, + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Sublabel component position + # Position 30x30 because the default values are : + # - 10x10 for the sublabel component in the main label + # - 10x10 for the sublabel in the sublabel component + # - 10x10 for the component in the sublabel + '^FO30,30' + # Sublabel component format + '^A0N,10,10' + # Sublabel component contents + '^FD{contents}' + # Sublabel component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_repeatable_component_label_fixed_contents(self): + """ Check contents of a repeatable label component + + Check that a fixed value is repeated each time + """ + label = self.new_label({ + 'model_id': self.env.ref( + 'printer_zpl2.model_printing_label_zpl2').id, + }) + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'data': '"' + data + '"', + 'repeat': True, + 'repeat_count': 3, + 'repeat_offset_y': 15, + }) + contents = label._generate_zpl2_data(label) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # First component position + '^FO10,10' + # First component format + '^A0N,10,10' + # First component contents + '^FD{contents}' + # First component end + '^FS\n' + # Second component position + '^FO10,25' + # Second component format + '^A0N,10,10' + # Second component contents + '^FD{contents}' + # Second component end + '^FS\n' + # Third component position + '^FO10,40' + # Third component format + '^A0N,10,10' + # Third component contents + '^FD{contents}' + # Third component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_repeatable_component_label_iterable_contents(self): + """ Check contents of a repeatable label component + + Check that an iterable contents (list, tuple, etc.) is browsed + If the repeat_count is higher than the value length, all values are + displayed + """ + label = self.new_label({ + 'model_id': self.env.ref( + 'printer_zpl2.model_printing_label_zpl2').id, + }) + data = ['First text', 'Second text', 'Third text'] + self.new_component({ + 'label_id': label.id, + 'data': str(data), + 'repeat': True, + 'repeat_offset': 1, + 'repeat_count': 3, + 'repeat_offset_y': 15, + }) + contents = label._generate_zpl2_data(label) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # First component position + '^FO10,10' + # First component format + '^A0N,10,10' + # First component contents + '^FD{contents[1]}' + # First component end + '^FS\n' + # Second component position + '^FO10,25' + # Second component format + '^A0N,10,10' + # Second component contents + '^FD{contents[2]}' + # Second component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_repeatable_component_label_iterable_offset(self): + """ Check contents of a repeatable label component with an offset + + Check that an iterable contents (list, tuple, etc.) is browsed + If the repeat_count is higher than the value length, all values are + displayed + """ + label = self.new_label({ + 'model_id': self.env.ref( + 'printer_zpl2.model_printing_label_zpl2').id, + }) + data = ['Text {value}'.format(value=ind) for ind in range(20)] + self.new_component({ + 'label_id': label.id, + 'data': str(data), + 'repeat': True, + 'repeat_offset': 10, + 'repeat_count': 3, + 'repeat_offset_y': 15, + }) + contents = label._generate_zpl2_data(label) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # First component position + '^FO10,10' + # First component format + '^A0N,10,10' + # First component contents + '^FD{contents[10]}' + # First component end + '^FS\n' + # Second component position + '^FO10,25' + # Second component format + '^A0N,10,10' + # Second component contents + '^FD{contents[11]}' + # Second component end + '^FS\n' + # Third component position + '^FO10,40' + # Third component format + '^A0N,10,10' + # Third component contents + '^FD{contents[12]}' + # Third component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_repeatable_sublabel_contents(self): + """ Check contents of a repeatable sublabel label component """ + sublabel = self.new_label({ + 'name': 'Sublabel', + 'model_id': self.env.ref( + 'printer_zpl2.model_printing_label_zpl2_component').id, + }) + self.new_component({ + 'label_id': sublabel.id, + 'name': 'Components name', + 'data': 'object.name', + }) + self.new_component({ + 'label_id': sublabel.id, + 'name': 'Components data', + 'data': 'object.data', + 'origin_x': 50, + }) + label = self.new_label({ + 'model_id': self.env.ref( + 'printer_zpl2.model_printing_label_zpl2').id, + }) + self.new_component({ + 'label_id': label.id, + 'name': 'Label name', + 'data': 'object.name', + }) + self.new_component({ + 'label_id': label.id, + 'name': 'Label components', + 'component_type': 'sublabel', + 'origin_x': 15, + 'origin_y': 30, + 'data': 'object.component_ids', + 'sublabel_id': sublabel.id, + 'repeat': True, + 'repeat_count': 3, + 'repeat_offset_y': 15, + }) + contents = label._generate_zpl2_data(label) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Label name component position + '^FO10,10' + # Label name component format + '^A0N,10,10' + # Label name component contents + '^FD{label.name}' + # Label name component end + '^FS\n' + # First component name component position + '^FO35,50' + # First component name component format + '^A0N,10,10' + # First component name component contents + '^FD{label.component_ids[0].name}' + # First component name component end + '^FS\n' + # First component data component position + '^FO75,50' + # First component data component format + '^A0N,10,10' + # First component data component contents + '^FD{label.component_ids[0].data}' + # First component data component end + '^FS\n' + # Second component name component position + '^FO35,65' + # Second component name component format + '^A0N,10,10' + # Second component name component contents + '^FD{label.component_ids[1].name}' + # Second component name component end + '^FS\n' + # Second component data component position + '^FO75,65' + # Second component data component format + '^A0N,10,10' + # Second component data component contents + '^FD{label.component_ids[1].data}' + # Second component data component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(label=label)) + + def test_text_label_contents(self): + """ Check contents of a text label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^A0N,10,10' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_reversed_text_label_contents(self): + """ Check contents of a text label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'data': '"' + data + '"', + 'reverse_print': True, + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^A0N,10,10' + # Reverse print argument + '^FR' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_block_text_label_contents(self): + """ Check contents of a text label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'data': '"' + data + '"', + 'in_block': True, + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^A0N,10,10' + # Block definition + '^FB0,1,0,L,0' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_rectangle_label_contents(self): + """ Check contents of a rectangle label """ + label = self.new_label() + self.new_component({ + 'label_id': label.id, + 'component_type': 'rectangle', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^GB1,1,1,B,0' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ') + + def test_circle_label_contents(self): + """ Check contents of a circle label """ + label = self.new_label() + self.new_component({ + 'label_id': label.id, + 'component_type': 'circle', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^GC3,2,B' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ') + + def test_code11_barcode_label_contents(self): + """ Check contents of a code 11 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_11', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B1N,N,0,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_2of5_barcode_label_contents(self): + """ Check contents of a interleaved 2 of 5 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'interleaved_2_of_5', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B2N,0,N,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_code39_barcode_label_contents(self): + """ Check contents of a code 39 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_39', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B3N,N,0,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_code49_barcode_label_contents(self): + """ Check contents of a code 49 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_49', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B4N,0,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_code49_barcode_label_contents_line(self): + """ Check contents of a code 49 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_49', + 'data': '"' + data + '"', + 'interpretation_line': True, + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B4N,0,B' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_code49_barcode_label_contents_with_above(self): + """ Check contents of a code 49 barconde label + with interpretation line above + """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_49', + 'data': '"' + data + '"', + 'interpretation_line': True, + 'interpretation_line_above': True, + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B4N,0,A' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_pdf417_barcode_label_contents(self): + """ Check contents of a pdf417 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'pdf417', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B7N,0,0,0,0,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_ean8_barcode_label_contents(self): + """ Check contents of a ean-8 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'ean-8', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B8N,0,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_upce_barcode_label_contents(self): + """ Check contents of a upc-e barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'upc-e', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^B9N,0,N,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_code128_barcode_label_contents(self): + """ Check contents of a code 128 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'code_128', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^BCN,0,N,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) + + def test_ean13_barcode_label_contents(self): + """ Check contents of a ean-13 barcode label """ + label = self.new_label() + data = 'Some text' + self.new_component({ + 'label_id': label.id, + 'component_type': 'ean-13', + 'data': '"' + data + '"', + }) + contents = label._generate_zpl2_data(self.printer) + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Barcode default format + '^BY2,3.0' + # Component position + '^FO10,10' + # Component format + '^BEN,0,N,N' + # Component contents + '^FD{contents}' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ'.format(contents=data)) diff --git a/printer_zpl2/tests/test_wizard_print_record_label.py b/printer_zpl2/tests/test_wizard_print_record_label.py new file mode 100644 index 0000000..1293a40 --- /dev/null +++ b/printer_zpl2/tests/test_wizard_print_record_label.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import mock + +from openerp.tests.common import TransactionCase + + +model = 'openerp.addons.base_report_to_printer.models.printing_server' + + +class TestWizardPrintRecordLabel(TransactionCase): + def setUp(self): + super(TestWizardPrintRecordLabel, self).setUp() + self.Model = self.env['wizard.print.record.label'] + self.server = self.env['printing.server'].create({}) + self.printer = self.env['printing.printer'].create({ + 'name': 'Printer', + 'server_id': self.server.id, + 'system_name': 'Sys Name', + 'default': True, + 'status': 'unknown', + 'status_message': 'Msg', + 'model': 'res.users', + 'location': 'Location', + 'uri': 'URI', + }) + self.label = self.env['printing.label.zpl2'].create({ + 'name': 'ZPL II Label', + 'model_id': self.env.ref( + 'base_report_to_printer.model_printing_printer').id, + }) + + @mock.patch('%s.cups' % model) + def test_print_record_label(self, cups): + """ Check that printing a label using the generic wizard works """ + wizard_obj = self.Model.with_context( + active_model='printing.printer', + active_id=self.printer.id, + active_ids=[self.printer.id], + ) + wizard = wizard_obj.create({}) + self.assertEqual(wizard.printer_id, self.printer) + self.assertEqual(wizard.label_id, self.label) + wizard.print_label() + 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 + when there are multiple possible values + """ + self.env['printing.printer'].create({ + 'name': 'Other_Printer', + 'server_id': self.server.id, + 'system_name': 'Sys Name', + 'default': True, + 'status': 'unknown', + 'status_message': 'Msg', + 'model': 'res.users', + 'location': 'Location', + 'uri': 'URI', + }) + self.env['printing.label.zpl2'].create({ + 'name': 'Other ZPL II Label', + 'model_id': self.env.ref( + 'base_report_to_printer.model_printing_printer').id, + }) + wizard_obj = self.Model.with_context( + active_model='printing.printer', + active_id=self.printer.id, + active_ids=[self.printer.id], + ) + values = wizard_obj.default_get(['printer_id', 'label_id']) + self.assertEqual(values.get('printer_id', False), False) + self.assertEqual(values.get('label_id', False), False) + + def test_wizard_multiple_labels_but_on_different_models(self): + """ Check that label_id is automatically filled when there are multiple + labels, but only one on the right model + """ + self.env['printing.label.zpl2'].create({ + 'name': 'Other ZPL II Label', + 'model_id': self.env.ref('base.model_res_users').id, + }) + wizard_obj = self.Model.with_context( + active_model='printing.printer', + active_id=self.printer.id, + active_ids=[self.printer.id], + ) + wizard = wizard_obj.create({}) + self.assertEqual(wizard.label_id, self.label) diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml new file mode 100644 index 0000000..1043b8b --- /dev/null +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -0,0 +1,144 @@ + + + + + + + printing.label.zpl2.tree + printing.label.zpl2 + + + + + + + + + printing.label.zpl2.form + printing.label.zpl2 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + printing.label.zpl2.search + printing.label.zpl2 + + + + + + + + + ZPL II + ir.actions.act_window + printing.label.zpl2 + form + tree,form + + [] + {} + + + + + form + + + + + + tree + + + +
diff --git a/printer_zpl2/wizard/__init__.py b/printer_zpl2/wizard/__init__.py new file mode 100644 index 0000000..5c68984 --- /dev/null +++ b/printer_zpl2/wizard/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import print_record_label diff --git a/printer_zpl2/wizard/print_record_label.py b/printer_zpl2/wizard/print_record_label.py new file mode 100644 index 0000000..121ded0 --- /dev/null +++ b/printer_zpl2/wizard/print_record_label.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 SYLEAM () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, api, fields + + +class PrintRecordLabel(models.TransientModel): + _name = 'wizard.print.record.label' + _description = 'Print Record Label' + + printer_id = fields.Many2one( + comodel_name='printing.printer', string='Printer', required=True, + help='Printer used to print the labels.') + label_id = fields.Many2one( + comodel_name='printing.label.zpl2', string='Label', required=True, + domain=lambda self: [ + ('model_id.model', '=', self.env.context.get('active_model'))], + help='Label to print.') + + @api.model + def default_get(self, fields_list): + values = super(PrintRecordLabel, self).default_get(fields_list) + + # Automatically select the printer and label, if only one is available + printers = self.env['printing.printer'].search([]) + if len(printers) == 1: + values['printer_id'] = printers.id + + labels = self.env['printing.label.zpl2'].search([ + ('model_id.model', '=', self.env.context.get('active_model')), + ]) + if len(labels) == 1: + values['label_id'] = labels.id + + return values + + @api.multi + def print_label(self): + """ Prints a label per selected record """ + record_model = self.env.context['active_model'] + for record_id in self.env.context['active_ids']: + record = self.env[record_model].browse(record_id) + self.label_id.print_label(self.printer_id, record) diff --git a/printer_zpl2/wizard/print_record_label.xml b/printer_zpl2/wizard/print_record_label.xml new file mode 100644 index 0000000..ed64c90 --- /dev/null +++ b/printer_zpl2/wizard/print_record_label.xml @@ -0,0 +1,30 @@ + + + + + wizard.print.record.label.form + wizard.print.record.label + +
+ + + + +
+ + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
- printing.label.zpl2.search printing.label.zpl2 + diff --git a/printer_zpl2/wizard/__init__.py b/printer_zpl2/wizard/__init__.py index 5c68984..c9a79f0 100644 --- a/printer_zpl2/wizard/__init__.py +++ b/printer_zpl2/wizard/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2016 SYLEAM () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/printer_zpl2/wizard/print_record_label.py b/printer_zpl2/wizard/print_record_label.py index bf39b0f..1944960 100644 --- a/printer_zpl2/wizard/print_record_label.py +++ b/printer_zpl2/wizard/print_record_label.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2016 SYLEAM () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -38,7 +37,6 @@ class PrintRecordLabel(models.TransientModel): return values - @api.multi def print_label(self): """ Prints a label per selected record """ record_model = self.env.context['active_model'] diff --git a/printer_zpl2/wizard/print_record_label.xml b/printer_zpl2/wizard/print_record_label.xml index ed64c90..4f19936 100644 --- a/printer_zpl2/wizard/print_record_label.xml +++ b/printer_zpl2/wizard/print_record_label.xml @@ -13,7 +13,10 @@ - From 59be9bc04885ff9ceb38555191b186114e18d787 Mon Sep 17 00:00:00 2001 From: Sylvain GARANCHER Date: Tue, 13 Feb 2018 16:57:56 +0100 Subject: [PATCH 16/46] [IMP] Add diagonal lines management --- printer_zpl2/models/printing_label_zpl2.py | 10 +++++++ .../models/printing_label_zpl2_component.py | 7 +++++ .../tests/test_printing_label_zpl2.py | 29 +++++++++++++++++++ printer_zpl2/views/printing_label_zpl2.xml | 13 +++++---- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py index 9552d8d..e8bf697 100644 --- a/printer_zpl2/models/printing_label_zpl2.py +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -123,6 +123,16 @@ class PrintingLabelZpl2(models.Model): zpl2.ARG_COLOR: component.color, zpl2.ARG_ROUNDING: component.rounding, }) + elif component.component_type == 'diagonal': + label_data.graphic_diagonal_line( + component_offset_x, component_offset_y, { + zpl2.ARG_WIDTH: component.width, + zpl2.ARG_HEIGHT: component.height, + zpl2.ARG_THICKNESS: component.thickness, + zpl2.ARG_COLOR: component.color, + zpl2.ARG_DIAGONAL_ORIENTATION: + component.diagonal_orientation, + }) elif component.component_type == 'graphic': image = component.graphic_image or data pil_image = Image.open(io.BytesIO( diff --git a/printer_zpl2/models/printing_label_zpl2_component.py b/printer_zpl2/models/printing_label_zpl2_component.py index 903169e..efdd593 100644 --- a/printer_zpl2/models/printing_label_zpl2_component.py +++ b/printer_zpl2/models/printing_label_zpl2_component.py @@ -42,6 +42,7 @@ class PrintingLabelZpl2Component(models.Model): selection=[ ('text', 'Text'), ('rectangle', 'Rectangle / Line'), + ('diagonal', 'Diagonal Line'), ('circle', 'Circle'), ('graphic', 'Graphic'), (zpl2.BARCODE_CODE_11, 'Code 11'), @@ -85,6 +86,12 @@ class PrintingLabelZpl2Component(models.Model): (zpl2.ORIENTATION_BOTTOM_UP, 'Read from Bottom up'), ], required=True, default=zpl2.ORIENTATION_NORMAL, help='Orientation of the barcode.') + diagonal_orientation = fields.Selection( + selection=[ + (zpl2.DIAGONAL_ORIENTATION_LEFT, 'Left (\\)'), + (zpl2.DIAGONAL_ORIENTATION_RIGHT, 'Right (/)'), + ], default=zpl2.DIAGONAL_ORIENTATION_LEFT, + help='Orientation of the diagonal line.') check_digits = fields.Boolean( help='Check if you want to compute and print the check digit.') height = fields.Integer( diff --git a/printer_zpl2/tests/test_printing_label_zpl2.py b/printer_zpl2/tests/test_printing_label_zpl2.py index ec610fe..7418382 100644 --- a/printer_zpl2/tests/test_printing_label_zpl2.py +++ b/printer_zpl2/tests/test_printing_label_zpl2.py @@ -527,6 +527,35 @@ class TestPrintingLabelZpl2(TransactionCase): # Label end '^XZ') + def test_diagonal_line_label_contents(self): + """ Check contents of a diagonal line label """ + label = self.new_label() + self.new_component({ + 'label_id': label.id, + 'component_type': 'diagonal', + }) + contents = label._generate_zpl2_data(self.printer).decode("utf-8") + self.assertEqual( + contents, + # Label start + '^XA\n' + # Print width + '^PW480\n' + # UTF-8 encoding + '^CI28\n' + # Label position + '^LH10,10\n' + # Component position + '^FO10,10' + # Component format + '^GD3,3,1,B,L' + # Component end + '^FS\n' + # Recall last saved parameters + '^JUR\n' + # Label end + '^XZ') + def test_circle_label_contents(self): """ Check contents of a circle label """ label = self.new_label() diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml index fd7d04c..9dd679e 100644 --- a/printer_zpl2/views/printing_label_zpl2.xml +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -64,24 +64,25 @@ - + - + - + - - + + + - + From 226d05a0521bb641122845fae4266272d9ad6628 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Tue, 23 Jan 2018 00:28:50 +0100 Subject: [PATCH 17/46] [IMP] Add a preview on the label using labelary.com --- printer_zpl2/models/printing_label_zpl2.py | 82 ++++++++++++++++++++- printer_zpl2/tests/__init__.py | 1 + printer_zpl2/tests/test_test_mode.py | 85 ++++++++++++++++++++++ printer_zpl2/views/printing_label_zpl2.xml | 24 ++++++ 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 printer_zpl2/tests/test_test_mode.py diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py index e8bf697..a12cd17 100644 --- a/printer_zpl2/models/printing_label_zpl2.py +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -6,8 +6,9 @@ import base64 import datetime import io import logging +import requests from PIL import Image, ImageOps -from odoo import exceptions, fields, models, _ +from odoo import api, exceptions, fields, models, _ from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -48,6 +49,19 @@ class PrintingLabelZpl2(models.Model): default=True) action_window_id = fields.Many2one( comodel_name='ir.actions.act_window', string='Action', readonly=True) + test_labelary_mode = fields.Boolean(string='Mode Labelary') + record_id = fields.Integer(string='Record ID', default=1) + extra = fields.Text(string="Extra", default='{}') + labelary_image = fields.Binary(string='Image from Labelary', readonly=True) + labelary_dpmm = fields.Selection( + selection=[ + ('6dpmm', '6dpmm (152 pdi)'), + ('8dpmm', '8dpmm (203 dpi)'), + ('12dpmm', '12dpmm (300 pdi)'), + ('24dpmm', '24dpmm (600 dpi)'), + ], string='Print density', required=True, default='8dpmm') + labelary_width = fields.Float(string='Width in mm', default=140) + labelary_height = fields.Float(string='Height in mm', default=70) def _generate_zpl2_components_data( self, label_data, record, page_number=1, page_count=1, @@ -205,10 +219,12 @@ class PrintingLabelZpl2(models.Model): self.ensure_one() label_data = zpl2.Zpl2() + labelary_emul = extra.get('labelary_emul', False) for page_number in range(page_count): # Initialize printer's configuration label_data.label_start() - label_data.print_width(self.width) + if not labelary_emul: + label_data.print_width(self.width) label_data.label_encoding() label_data.label_home(self.origin_x, self.origin_y) @@ -255,3 +271,65 @@ class PrintingLabelZpl2(models.Model): def unlink_action(self): self.mapped('action_window_id').unlink() + + def _get_record(self): + self.ensure_one() + Obj = self.env[self.model_id.model] + record = Obj.search([('id', '=', self.record_id)], limit=1) + if not record: + record = Obj.search([], limit=1, order='id desc') + self.record_id = record.id + + return record + + @api.onchange( + 'record_id', 'labelary_dpmm', 'labelary_width', 'labelary_height', + 'component_ids', 'origin_x', 'origin_y') + def _on_change_labelary(self): + self.ensure_one() + if not(self.test_labelary_mode and self.record_id and + self.labelary_width and self.labelary_height and + self.labelary_dpmm and self.component_ids): + return + record = self._get_record() + if record: + # If case there an error (in the data field with the safe_eval + # for exemple) the new component or the update is not lost. + try: + url = 'http://api.labelary.com/v1/printers/' \ + '{dpmm}/labels/{width}x{height}/0/' + width = round(self.labelary_width / 25.4, 2) + height = round(self.labelary_height / 25.4, 2) + url = url.format( + dpmm=self.labelary_dpmm, width=width, height=height) + extra = safe_eval(self.extra, {'env': self.env}) + zpl_file = self._generate_zpl2_data( + record, labelary_emul=True, **extra) + files = {'file': zpl_file} + headers = {'Accept': 'image/png'} + response = requests.post( + url, headers=headers, files=files, stream=True) + if response.status_code == 200: + # Add a padd + im = Image.open(io.BytesIO(response.content)) + im_size = im.size + new_im = Image.new( + 'RGB', (im_size[0] + 2, im_size[1] + 2), + (164, 164, 164)) + new_im.paste(im, (1, 1)) + imgByteArr = io.BytesIO() + new_im.save(imgByteArr, format='PNG') + self.labelary_image = base64.b64encode( + imgByteArr.getvalue()) + else: + return {'warning': { + 'title': _('Error with Labelary API.'), + 'message': response.status_code, + }} + + except Exception as e: + self.labelary_image = False + return {'warning': { + 'title': _('Some thing is wrong.'), + 'message': e, + }} diff --git a/printer_zpl2/tests/__init__.py b/printer_zpl2/tests/__init__.py index cc4e18f..03aa9ce 100644 --- a/printer_zpl2/tests/__init__.py +++ b/printer_zpl2/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_printing_label_zpl2 from . import test_wizard_print_record_label from . import test_generate_action +from . import test_test_mode diff --git a/printer_zpl2/tests/test_test_mode.py b/printer_zpl2/tests/test_test_mode.py new file mode 100644 index 0000000..20abdb5 --- /dev/null +++ b/printer_zpl2/tests/test_test_mode.py @@ -0,0 +1,85 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + +model = 'odoo.addons.base_report_to_printer.models.printing_server' + + +class TestWizardPrintRecordLabel(TransactionCase): + def setUp(self): + super(TestWizardPrintRecordLabel, self).setUp() + self.Model = self.env['wizard.print.record.label'] + self.server = self.env['printing.server'].create({}) + self.printer = self.env['printing.printer'].create({ + 'name': 'Printer', + 'server_id': self.server.id, + 'system_name': 'Sys Name', + 'default': True, + 'status': 'unknown', + 'status_message': 'Msg', + 'model': 'res.users', + 'location': 'Location', + 'uri': 'URI', + }) + self.label = self.env['printing.label.zpl2'].create({ + 'name': 'ZPL II Label', + 'model_id': self.env.ref( + 'base_report_to_printer.model_printing_printer').id, + }) + + def test_get_record(self): + """ Check if return a record """ + self.label.record_id = 10 + res = self.label._get_record() + + Obj = self.env[self.label.model_id.model] + record = Obj.search([('id', '=', self.label.record_id)], limit=1) + if not record: + record = Obj.search([], limit=1, order='id desc') + self.assertEqual(res, record) + + def test_emulation_without_params(self): + """ Check if not execute next if not in this mode """ + self.label.test_labelary_mode = False + self.label._on_change_labelary() + self.assertIs(self.label.labelary_image, None) + + def test_emulation_with_bad_header(self): + """ Check if bad header """ + self.label.test_labelary_mode = True + self.label.labelary_width = 80 + self.label.labelary_dpmm = '8dpmm' + self.label.labelary_height = 10000000 + self.env['printing.label.zpl2.component'].create({ + 'name': 'ZPL II Label', + 'label_id': self.label.id, + 'data': '"Test"'}) + self.label._on_change_labelary() + self.assertFalse(self.label.labelary_image) + + def test_emulation_with_bad_data_compute(self): + """ Check if bad data compute """ + self.label.test_labelary_mode = True + self.label.labelary_width = 80 + self.label.labelary_height = 30 + self.label.labelary_dpmm = '8dpmm' + component = self.env['printing.label.zpl2.component'].create({ + 'name': 'ZPL II Label', + 'label_id': self.label.id, + 'data': 'wrong_data'}) + self.label._on_change_labelary() + component.unlink() + self.assertIs(self.label.labelary_image, None) + + def test_emulation_with_good_data(self): + """ Check if ok """ + self.label.test_labelary_mode = True + self.label.labelary_width = 80 + self.label.labelary_height = 30 + self.label.labelary_dpmm = '8dpmm' + self.env['printing.label.zpl2.component'].create({ + 'name': 'ZPL II Label', + 'label_id': self.label.id, + 'data': '"good_data"', }) + self.label._on_change_labelary() + self.assertTrue(self.label.labelary_image) diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml index 9dd679e..2263e06 100644 --- a/printer_zpl2/views/printing_label_zpl2.xml +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -126,6 +126,30 @@ + + +

+ Note : It is an emulation from http://labelary.com/, the result on printer can be diffrent. +

+
+ + + + + + + + + + + + + + + + + + From 2318cc670e0e949a22dd26a86465b53749d7e854 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Sat, 20 Jan 2018 23:20:11 +0100 Subject: [PATCH 18/46] [IMP] Add wizard to import ZPL2 --- printer_zpl2/README.rst | 1 + printer_zpl2/__manifest__.py | 1 + printer_zpl2/models/printing_label_zpl2.py | 11 + printer_zpl2/tests/__init__.py | 1 + printer_zpl2/tests/test_wizard_import_zpl2.py | 97 ++++ printer_zpl2/views/printing_label_zpl2.xml | 3 + printer_zpl2/wizard/__init__.py | 1 + printer_zpl2/wizard/wizard_import_zpl2.py | 437 ++++++++++++++++++ printer_zpl2/wizard/wizard_import_zpl2.xml | 24 + 9 files changed, 576 insertions(+) create mode 100644 printer_zpl2/tests/test_wizard_import_zpl2.py create mode 100644 printer_zpl2/wizard/wizard_import_zpl2.py create mode 100644 printer_zpl2/wizard/wizard_import_zpl2.xml diff --git a/printer_zpl2/README.rst b/printer_zpl2/README.rst index da103cc..e488011 100644 --- a/printer_zpl2/README.rst +++ b/printer_zpl2/README.rst @@ -24,6 +24,7 @@ To configure this module, you need to: #. Go to *Settings > Printing > Labels > ZPL II* #. Create new labels +#. Import ZPL2 code It's also possible to add a label printing wizard on any model by creating a new *ir.actions.act_window* record. For example, to add the printing wizard on the *product.product* model : diff --git a/printer_zpl2/__manifest__.py b/printer_zpl2/__manifest__.py index 3fe04b4..44e7b51 100644 --- a/printer_zpl2/__manifest__.py +++ b/printer_zpl2/__manifest__.py @@ -18,6 +18,7 @@ 'security/ir.model.access.csv', 'views/printing_label_zpl2.xml', 'wizard/print_record_label.xml', + 'wizard/wizard_import_zpl2.xml', ], 'installable': True, } diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py index a12cd17..d175154 100644 --- a/printer_zpl2/models/printing_label_zpl2.py +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -272,6 +272,17 @@ class PrintingLabelZpl2(models.Model): def unlink_action(self): self.mapped('action_window_id').unlink() + def import_zpl2(self): + self.ensure_one() + return { + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'wizard.import.zpl2', + 'type': 'ir.actions.act_window', + 'target': 'new', + 'context': {'default_label_id': self.id}, + } + def _get_record(self): self.ensure_one() Obj = self.env[self.model_id.model] diff --git a/printer_zpl2/tests/__init__.py b/printer_zpl2/tests/__init__.py index 03aa9ce..78b9bd8 100644 --- a/printer_zpl2/tests/__init__.py +++ b/printer_zpl2/tests/__init__.py @@ -5,3 +5,4 @@ from . import test_printing_label_zpl2 from . import test_wizard_print_record_label from . import test_generate_action from . import test_test_mode +from . import test_wizard_import_zpl2 diff --git a/printer_zpl2/tests/test_wizard_import_zpl2.py b/printer_zpl2/tests/test_wizard_import_zpl2.py new file mode 100644 index 0000000..ce1a8e3 --- /dev/null +++ b/printer_zpl2/tests/test_wizard_import_zpl2.py @@ -0,0 +1,97 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestWizardImportZpl2(TransactionCase): + def setUp(self): + super(TestWizardImportZpl2, self).setUp() + self.Model = self.env['wizard.print.record.label'] + self.server = self.env['printing.server'].create({}) + self.printer = self.env['printing.printer'].create({ + 'name': 'Printer', + 'server_id': self.server.id, + 'system_name': 'Sys Name', + 'default': True, + 'status': 'unknown', + 'status_message': 'Msg', + 'model': 'res.users', + 'location': 'Location', + 'uri': 'URI', + }) + self.label = self.env['printing.label.zpl2'].create({ + 'name': 'ZPL II Label', + 'model_id': self.env.ref( + 'base_report_to_printer.model_printing_printer').id, + }) + + def test_open_wizard(self): + """ open wizard from label""" + res = self.label.import_zpl2() + self.assertEqual( + res.get('context').get('default_label_id'), + self.label.id) + + def test_wizard_import_zpl2(self): + """ Import ZPL2 from wizard """ + zpl_data = ("^XA\n" + "^CI28\n" + "^LH0,0\n" + "^CF0\n" + "^CFA,10\n" + "^CFB,10,10\n" + "^FO10,10^A0N,30,30^FDTEXT^FS\n" + "^BY2,3.0^FO600,60^BCN,30,N,N,N" + "^FDAJFGJAGJVJVHK^FS\n" + "^FO10,40^A0N,20,40^FB150,2,1,J,0^FDTEXT BLOCK^FS\n" + "^FO300,10^GC100,3,B^FS\n" + "^FO10,200^GB200,200,100,B,0^FS\n" + "^FO10,60^GFA,16.0,16.0,2.0," + "b'FFC0FFC0FFC0FFC0FFC0FFC0FFC0FFC0'^FS\n" + "^FO10,200^GB300,100,6,W,0^FS\n" + "^BY2,3.0^FO300,10^B1N,N,30,N,N^FD678987656789^FS\n" + "^BY2,3.0^FO300,70^B2N,30,Y,Y,N^FD567890987768^FS\n" + "^BY2,3.0^FO300,120^B3N,N,30,N,N^FD98765456787656^FS\n" + "^BY2,3.0^FO300,200^BQN,2,5,Q,7" + "^FDMM,A876567897656787658654645678^FS\n" + "^BY2,3.0^FO400,250^BER,40,Y,Y^FD9876789987654567^FS\n" + "^BY2,3.0^FO350,250^B7N,20,0,0,0,N^FD8765678987656789^FS\n" + "^BY2,3.0^FO700,10^B9N,20,N,N,N^FD87657890987654^FS\n" + "^BY2,3.0^FO600,200^B4N,50,N^FD7654567898765678^FS\n" + "^BY2,3.0^FO600,300^BEN,50,Y,Y^FD987654567890876567^FS\n" + "^FO300,300^AGI,50,50^FR^FDINVERTED^FS\n" + "^BY2,3.0^FO700,200^B8,50,N,N^FD987609876567^FS\n" + "^JUR\n" + "^XZ") + + vals = {'label_id': self.label.id, + 'delete_component': True, + 'data': zpl_data} + wizard = self.env['wizard.import.zpl2'].create(vals) + wizard.import_zpl2() + self.assertEqual( + 18, + len(self.label.component_ids)) + + def test_wizard_import_zpl2_add(self): + """ Import ZPL2 from wizard ADD""" + self.env['printing.label.zpl2.component'].create({ + 'name': 'ZPL II Label', + 'label_id': self.label.id, + 'data': '"data"', + 'sequence': 10}) + zpl_data = ("^XA\n" + "^CI28\n" + "^LH0,0\n" + "^FO10,10^A0N,30,30^FDTEXT^FS\n" + "^JUR\n" + "^XZ") + + vals = {'label_id': self.label.id, + 'delete_component': False, + 'data': zpl_data} + wizard = self.env['wizard.import.zpl2'].create(vals) + wizard.import_zpl2() + self.assertEqual( + 2, + len(self.label.component_ids)) diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml index 2263e06..0272173 100644 --- a/printer_zpl2/views/printing_label_zpl2.xml +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -17,6 +17,9 @@ printing.label.zpl2
+
+
diff --git a/printer_zpl2/wizard/__init__.py b/printer_zpl2/wizard/__init__.py index c9a79f0..0389ab1 100644 --- a/printer_zpl2/wizard/__init__.py +++ b/printer_zpl2/wizard/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import print_record_label +from . import wizard_import_zpl2 diff --git a/printer_zpl2/wizard/wizard_import_zpl2.py b/printer_zpl2/wizard/wizard_import_zpl2.py new file mode 100644 index 0000000..8e5b8b0 --- /dev/null +++ b/printer_zpl2/wizard/wizard_import_zpl2.py @@ -0,0 +1,437 @@ +import logging +import re +import base64 +import binascii +import io + + +from PIL import Image, ImageOps +from odoo import models, api, fields, _ + +_logger = logging.getLogger(__name__) + +try: + import zpl2 +except ImportError: + _logger.debug('Cannot `import zpl2`.') + + +def _compute_arg(data, arg): + vals = {} + for i, d in enumerate(data.split(',')): + vals[arg[i]] = d + return vals + + +def _field_origin(data): + if data[:2] == 'FO': + position = data[2:] + vals = _compute_arg(position, ['origin_x', 'origin_y']) + return vals + return {} + + +def _font_format(data): + if data[:1] == 'A': + data = data.split(',') + vals = {} + if len(data[0]) > 1: + vals[zpl2.ARG_FONT] = data[0][1] + if len(data[0]) > 2: + vals[zpl2.ARG_ORIENTATION] = data[0][2] + + if len(data) > 1: + vals[zpl2.ARG_HEIGHT] = data[1] + if len(data) > 2: + vals[zpl2.ARG_WIDTH] = data[2] + return vals + return {} + + +def _default_font_format(data): + if data[:2] == 'CF': + args = [ + zpl2.ARG_FONT, + zpl2.ARG_HEIGHT, + zpl2.ARG_WIDTH, + ] + vals = _compute_arg(data[2:], args) + if vals.get(zpl2.ARG_HEIGHT, False) \ + and not vals.get(zpl2.ARG_WIDTH, False): + vals.update({zpl2.ARG_WIDTH: vals.get(zpl2.ARG_HEIGHT)}) + else: + vals.update({zpl2.ARG_HEIGHT: 10, zpl2.ARG_HEIGHT: 10}) + return vals + return {} + + +def _field_block(data): + if data[:2] == 'FB': + vals = {zpl2.ARG_IN_BLOCK: True} + args = [ + zpl2.ARG_BLOCK_WIDTH, + zpl2.ARG_BLOCK_LINES, + zpl2.ARG_BLOCK_SPACES, + zpl2.ARG_BLOCK_JUSTIFY, + zpl2.ARG_BLOCK_LEFT_MARGIN, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _code11(data): + if data[:2] == 'B1': + vals = {'component_type': zpl2.BARCODE_CODE_11} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_CHECK_DIGITS, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _interleaved2of5(data): + if data[:2] == 'B2': + vals = {'component_type': zpl2.BARCODE_INTERLEAVED_2_OF_5} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + zpl2.ARG_CHECK_DIGITS, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _code39(data): + if data[:2] == 'B3': + vals = {'component_type': zpl2.BARCODE_CODE_39} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_CHECK_DIGITS, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _code49(data): + if data[:2] == 'B4': + vals = {'component_type': zpl2.BARCODE_CODE_49} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_STARTING_MODE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _pdf417(data): + if data[:2] == 'B7': + vals = {'component_type': zpl2.BARCODE_PDF417} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_SECURITY_LEVEL, + zpl2.ARG_COLUMNS_COUNT, + zpl2.ARG_ROWS_COUNT, + zpl2.ARG_TRUNCATE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _ean8(data): + if data[:2] == 'B8': + vals = {'component_type': zpl2.BARCODE_EAN_8} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _upce(data): + if data[:2] == 'B9': + vals = {'component_type': zpl2.BARCODE_UPC_E} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + zpl2.ARG_CHECK_DIGITS, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _code128(data): + if data[:2] == 'BC': + vals = {'component_type': zpl2.BARCODE_CODE_128} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + zpl2.ARG_CHECK_DIGITS, + zpl2.ARG_MODE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _ean13(data): + if data[:2] == 'BE': + vals = {'component_type': zpl2.BARCODE_EAN_13} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_HEIGHT, + zpl2.ARG_INTERPRETATION_LINE, + zpl2.ARG_INTERPRETATION_LINE_ABOVE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _qrcode(data): + if data[:2] == 'BQ': + vals = {'component_type': zpl2.BARCODE_QR_CODE} + args = [ + zpl2.ARG_ORIENTATION, + zpl2.ARG_MODEL, + zpl2.ARG_MAGNIFICATION_FACTOR, + zpl2.ARG_ERROR_CORRECTION, + zpl2.ARG_MASK_VALUE, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _default_barcode_field(data): + if data[:2] == 'BY': + args = [ + zpl2.ARG_MODULE_WIDTH, + zpl2.ARG_BAR_WIDTH_RATIO, + zpl2.ARG_HEIGHT, + ] + return _compute_arg(data[2:], args) + return {} + + +def _field_reverse_print(data): + if data[:2] == 'FR': + return {zpl2.ARG_REVERSE_PRINT: True} + return {} + + +def _graphic_box(data): + if data[:2] == 'GB': + vals = {'component_type': 'rectangle'} + args = [ + zpl2.ARG_WIDTH, + zpl2.ARG_HEIGHT, + zpl2.ARG_THICKNESS, + zpl2.ARG_COLOR, + zpl2.ARG_ROUNDING, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _graphic_circle(data): + if data[:2] == 'GC': + vals = {'component_type': 'circle'} + args = [ + zpl2.ARG_WIDTH, + zpl2.ARG_THICKNESS, + zpl2.ARG_COLOR, + ] + vals.update(_compute_arg(data[2:], args)) + return vals + return {} + + +def _graphic_field(data): + if data[:3] == 'GFA': + vals = {} + args = [ + 'compression', + 'total_bytes', + 'total_bytes', + 'bytes_per_row', + 'ascii_data', + ] + vals.update(_compute_arg(data[3:], args)) + + # Image + rawData = re.sub('[^A-F0-9]+', '', vals['ascii_data']) + rawData = binascii.unhexlify(rawData) + + width = int(float(vals['bytes_per_row']) * 8) + height = int(float(vals['total_bytes']) / width) * 8 + + img = Image.frombytes( + '1', (width, height), rawData, 'raw').convert('L') + img = ImageOps.invert(img) + + imgByteArr = io.BytesIO() + img.save(imgByteArr, format='PNG') + image = base64.b64encode(imgByteArr.getvalue()) + + return { + 'component_type': 'graphic', + 'graphic_image': image, + zpl2.ARG_WIDTH: width, + zpl2.ARG_HEIGHT: height, + } + return {} + + +def _get_data(data): + if data[:2] == 'FD': + return {'data': '"%s"' % data[2:]} + return {} + + +SUPPORTED_CODE = { + 'FO': {'method': _field_origin}, + 'FD': {'method': _get_data}, + 'A': {'method': _font_format}, + 'FB': {'method': _field_block}, + 'B1': {'method': _code11}, + 'B2': {'method': _interleaved2of5}, + 'B3': {'method': _code39}, + 'B4': {'method': _code49}, + 'B7': {'method': _pdf417}, + 'B8': {'method': _ean8}, + 'B9': {'method': _upce}, + 'BC': {'method': _code128}, + 'BE': {'method': _ean13}, + 'BQ': {'method': _qrcode}, + 'BY': { + 'method': _default_barcode_field, + 'default': [ + zpl2.BARCODE_CODE_11, + zpl2.BARCODE_INTERLEAVED_2_OF_5, + zpl2.BARCODE_CODE_39, + zpl2.BARCODE_CODE_49, + zpl2.BARCODE_PDF417, + zpl2.BARCODE_EAN_8, + zpl2.BARCODE_UPC_E, + zpl2.BARCODE_CODE_128, + zpl2.BARCODE_EAN_13, + zpl2.BARCODE_QR_CODE, + ], + }, + 'CF': {'method': _default_font_format, 'default': ['text']}, + 'FR': {'method': _field_reverse_print}, + 'GB': {'method': _graphic_box}, + 'GC': {'method': _graphic_circle}, + 'GFA': {'method': _graphic_field}, +} + + +class WizardImportZPl2(models.TransientModel): + _name = 'wizard.import.zpl2' + _description = 'Import ZPL2' + + label_id = fields.Many2one( + comodel_name='printing.label.zpl2', string='Label', + required=True, readonly=True,) + data = fields.Text( + required=True, help='Printer used to print the labels.') + delete_component = fields.Boolean( + string='Delete existing components', default=False) + + def _start_sequence(self): + sequences = self.mapped('label_id.component_ids.sequence') + if sequences: + return max(sequences) + 1 + return 0 + + def import_zpl2(self): + Zpl2Component = self.env['printing.label.zpl2.component'] + + if self.delete_component: + self.mapped('label_id.component_ids').unlink() + + Model = self.env['printing.label.zpl2.component'] + self.model_fields = Model.fields_get() + sequence = self._start_sequence() + default = {} + + for i, line in enumerate(self.data.split('\n')): + vals = {} + + args = line.split('^') + for arg in args: + for key, code in SUPPORTED_CODE.items(): + component_arg = code['method'](arg) + if component_arg: + if code.get('default', False): + for deft in code.get('default'): + default.update({deft: component_arg}) + else: + vals.update(component_arg) + break + + if vals: + if 'component_type' not in vals.keys(): + vals.update({'component_type': 'text'}) + + if vals['component_type'] in default.keys(): + vals.update(default[vals['component_type']]) + + vals = self._update_vals(vals) + + seq = sequence + i * 10 + vals.update({ + 'name': _('Import %s') % seq, + 'sequence': seq, + 'label_id': self.label_id.id, + }) + Zpl2Component.create(vals) + + def _update_vals(self, vals): + if 'orientation' in vals.keys() and vals['orientation'] == '': + vals['orientation'] = 'N' + + # Field + component = {} + for field, value in vals.items(): + if field in self.model_fields.keys(): + field_type = self.model_fields[field].get('type', False) + if field_type == 'boolean': + if value == '' or value == zpl2.BOOL_NO: + value = False + else: + value = True + if field_type in ('integer', 'float'): + value = float(value) + if field == 'model': + value = int(float(value)) + component.update({field: value}) + return component diff --git a/printer_zpl2/wizard/wizard_import_zpl2.xml b/printer_zpl2/wizard/wizard_import_zpl2.xml new file mode 100644 index 0000000..b718053 --- /dev/null +++ b/printer_zpl2/wizard/wizard_import_zpl2.xml @@ -0,0 +1,24 @@ + + + + wizard.import.zpl2.form + wizard.import.zpl2 + + + + + + + + + + + +
+
+ +
+
+
From 2f9de1f5f68a44ecb0c531f9fe2d0997c0e2701e Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Tue, 23 Jan 2018 00:41:35 +0100 Subject: [PATCH 19/46] [IMP] Add a test mode to print a label on write --- printer_zpl2/README.rst | 1 + printer_zpl2/models/printing_label_zpl2.py | 11 +++++++++++ printer_zpl2/tests/test_test_mode.py | 10 ++++++++++ printer_zpl2/views/printing_label_zpl2.xml | 9 +++++++-- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/printer_zpl2/README.rst b/printer_zpl2/README.rst index e488011..5400be5 100644 --- a/printer_zpl2/README.rst +++ b/printer_zpl2/README.rst @@ -25,6 +25,7 @@ To configure this module, you need to: #. Go to *Settings > Printing > Labels > ZPL II* #. Create new labels #. Import ZPL2 code +#. Use the Test Mode tab during the creation It's also possible to add a label printing wizard on any model by creating a new *ir.actions.act_window* record. For example, to add the printing wizard on the *product.product* model : diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py index d175154..e6b9609 100644 --- a/printer_zpl2/models/printing_label_zpl2.py +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -49,9 +49,12 @@ class PrintingLabelZpl2(models.Model): default=True) action_window_id = fields.Many2one( 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', readonly=True) labelary_dpmm = fields.Selection( selection=[ @@ -293,6 +296,14 @@ class PrintingLabelZpl2(models.Model): return record + def print_test_label(self): + for label in self: + if label.test_print_mode and label.record_id and label.printer_id: + record = label._get_record() + extra = safe_eval(label.extra, {'env': self.env}) + if record: + label.print_label(label.printer_id, record, **extra) + @api.onchange( 'record_id', 'labelary_dpmm', 'labelary_width', 'labelary_height', 'component_ids', 'origin_x', 'origin_y') diff --git a/printer_zpl2/tests/test_test_mode.py b/printer_zpl2/tests/test_test_mode.py index 20abdb5..870a837 100644 --- a/printer_zpl2/tests/test_test_mode.py +++ b/printer_zpl2/tests/test_test_mode.py @@ -1,4 +1,5 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import mock from odoo.tests.common import TransactionCase @@ -38,6 +39,15 @@ class TestWizardPrintRecordLabel(TransactionCase): record = Obj.search([], limit=1, order='id desc') self.assertEqual(res, record) + @mock.patch('%s.cups' % model) + def test_print_label_test(self, cups): + """ Check if print test """ + self.label.test_print_mode = True + self.label.printer_id = self.printer + self.label.record_id = 10 + self.label.print_test_label() + cups.Connection().printFile.assert_called_once() + def test_emulation_without_params(self): """ Check if not execute next if not in this mode """ self.label.test_labelary_mode = False diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml index 0272173..d22cea1 100644 --- a/printer_zpl2/views/printing_label_zpl2.xml +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -38,6 +38,9 @@ + +
@@ -36,6 +40,7 @@ + @@ -44,7 +49,7 @@ - + @@ -77,6 +82,7 @@ + diff --git a/printer_zpl2/wizard/wizard_import_zpl2.py b/printer_zpl2/wizard/wizard_import_zpl2.py index 7c647dd..6493564 100644 --- a/printer_zpl2/wizard/wizard_import_zpl2.py +++ b/printer_zpl2/wizard/wizard_import_zpl2.py @@ -9,7 +9,7 @@ import re from PIL import Image, ImageOps -from odoo import _ as translate, fields, models +from odoo import _, fields, models _logger = logging.getLogger(__name__) @@ -376,7 +376,7 @@ class WizardImportZPl2(models.TransientModel): args = line.split("^") for arg in args: - for _, code in SUPPORTED_CODE.items(): + for _key, code in SUPPORTED_CODE.items(): component_arg = code["method"](arg) if component_arg: if code.get("default", False): @@ -398,8 +398,9 @@ class WizardImportZPl2(models.TransientModel): seq = sequence + i * 10 vals.update( { - "name": translate("Import %s") % seq, + "name": _("Import %s") % seq, "sequence": seq, + "model": str(zpl2.MODEL_ENHANCED), "label_id": self.label_id.id, } ) @@ -423,5 +424,7 @@ class WizardImportZPl2(models.TransientModel): value = True if field_type in ("integer", "float"): value = float(value) + if field == "model": + value = int(float(value)) component.update({field: value}) return component From f920445f6a009fea7b4069130eef4748fe0bbe86 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Thu, 5 Mar 2020 20:41:33 +0100 Subject: [PATCH 32/46] [IMP] print_zpl2 : quick move --- printer_zpl2/models/printing_label_zpl2.py | 38 +- .../models/printing_label_zpl2_component.py | 16 + .../tests/test_printing_label_zpl2.py | 25 +- printer_zpl2/tests/test_test_mode.py | 10 +- .../tests/test_wizard_print_record_label.py | 4 +- printer_zpl2/views/printing_label_zpl2.xml | 330 +++++++++++++----- printer_zpl2/wizard/print_record_label.xml | 15 +- printer_zpl2/wizard/wizard_import_zpl2.xml | 17 +- 8 files changed, 320 insertions(+), 135 deletions(-) diff --git a/printer_zpl2/models/printing_label_zpl2.py b/printer_zpl2/models/printing_label_zpl2.py index 3c347e7..8f33ee9 100644 --- a/printer_zpl2/models/printing_label_zpl2.py +++ b/printer_zpl2/models/printing_label_zpl2.py @@ -70,7 +70,8 @@ class PrintingLabelZpl2(models.Model): 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", readonly=True) + labelary_image = fields.Binary(string='Image from Labelary', + compute='_compute_labelary_image') labelary_dpmm = fields.Selection( selection=[ ("6dpmm", "6dpmm (152 pdi)"), @@ -407,16 +408,14 @@ class PrintingLabelZpl2(models.Model): if record: label.print_label(label.printer_id, record, **extra) - @api.onchange( - "record_id", - "labelary_dpmm", - "labelary_width", - "labelary_height", - "component_ids", - "origin_x", - "origin_y", - ) - def _on_change_labelary(self): + @api.depends( + '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() + + def _generate_labelary_image(self): self.ensure_one() if not ( self.test_labelary_mode @@ -426,7 +425,7 @@ class PrintingLabelZpl2(models.Model): and self.labelary_dpmm and self.component_ids ): - return + return False record = self._get_record() if record: # If case there an error (in the data field with the safe_eval @@ -454,15 +453,12 @@ class PrintingLabelZpl2(models.Model): new_im.paste(im, (1, 1)) imgByteArr = io.BytesIO() new_im.save(imgByteArr, format="PNG") - self.labelary_image = base64.b64encode(imgByteArr.getvalue()) + return base64.b64encode(imgByteArr.getvalue()) else: - return { - "warning": { - "title": _("Error with Labelary API."), - "message": response.status_code, - } - } + _logger.warning( + _( + "Error with Labelary API. %s") % response.status_code) except Exception as e: - self.labelary_image = False - return {"warning": {"title": _("Some thing is wrong."), "message": e}} + _logger.warning(_("Error with Labelary API. %s") % e) + return False diff --git a/printer_zpl2/models/printing_label_zpl2_component.py b/printer_zpl2/models/printing_label_zpl2_component.py index 9b566fa..9ad4660 100644 --- a/printer_zpl2/models/printing_label_zpl2_component.py +++ b/printer_zpl2/models/printing_label_zpl2_component.py @@ -232,3 +232,19 @@ class PrintingLabelZpl2Component(models.Model): help="This field holds a static image to print. " "If not set, the data field is evaluated.", ) + + def action_plus_origin_x(self): + self.ensure_one() + self.origin_x += 10 + + def action_minus_origin_x(self): + self.ensure_one() + self.origin_x -= 10 + + def action_plus_origin_y(self): + self.ensure_one() + self.origin_y += 10 + + def action_minus_origin_y(self): + self.ensure_one() + self.origin_y -= 10 diff --git a/printer_zpl2/tests/test_printing_label_zpl2.py b/printer_zpl2/tests/test_printing_label_zpl2.py index 28c853f..1d67e8a 100644 --- a/printer_zpl2/tests/test_printing_label_zpl2.py +++ b/printer_zpl2/tests/test_printing_label_zpl2.py @@ -67,10 +67,10 @@ 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' + file_name = "test.zpl" label.print_label(self.printer, self.printer) cups.Connection().printFile.assert_called_once_with( - printer.system_name, file_name, file_name, options={} + self.printer.system_name, file_name, file_name, options={} ) def test_empty_label_contents(self): @@ -1170,3 +1170,24 @@ class TestPrintingLabelZpl2(TransactionCase): self.assertEqual( contents, "^XA\n" "^PW480\n" "^CI28\n" "^LH10,10\n" "^JUR\n" "^XZ" ) + + def test_zpl2_component_quick_move(self): + """ Check component quick move """ + label = self.new_label() + component = self.new_component( + { + "label_id": label.id, + "component_type": "zpl2_raw", + "data": '""', + "origin_x": 20, + "origin_y": 30, + } + ) + component.action_plus_origin_x() + self.assertEqual(30, component.origin_x) + component.action_minus_origin_x() + self.assertEqual(20, component.origin_x) + component.action_plus_origin_y() + self.assertEqual(40, component.origin_y) + component.action_minus_origin_y() + self.assertEqual(30, component.origin_y) diff --git a/printer_zpl2/tests/test_test_mode.py b/printer_zpl2/tests/test_test_mode.py index b5dc60a..d6d7c4e 100644 --- a/printer_zpl2/tests/test_test_mode.py +++ b/printer_zpl2/tests/test_test_mode.py @@ -51,16 +51,15 @@ 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() + 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={} + self.printer.system_name, file_name, file_name, options={} ) def test_emulation_without_params(self): """ Check if not execute next if not in this mode """ self.label.test_labelary_mode = False - self.label._on_change_labelary() self.assertIs(self.label.labelary_image, False) def test_emulation_with_bad_header(self): @@ -72,7 +71,6 @@ class TestWizardPrintRecordLabel(TransactionCase): self.env["printing.label.zpl2.component"].create( {"name": "ZPL II Label", "label_id": self.label.id, "data": '"Test"'} ) - self.label._on_change_labelary() self.assertFalse(self.label.labelary_image) def test_emulation_with_bad_data_compute(self): @@ -84,7 +82,6 @@ class TestWizardPrintRecordLabel(TransactionCase): component = self.env["printing.label.zpl2.component"].create( {"name": "ZPL II Label", "label_id": self.label.id, "data": "wrong_data"} ) - self.label._on_change_labelary() component.unlink() self.assertIs(self.label.labelary_image, False) @@ -97,5 +94,4 @@ class TestWizardPrintRecordLabel(TransactionCase): self.env["printing.label.zpl2.component"].create( {"name": "ZPL II Label", "label_id": self.label.id, "data": '"good_data"'} ) - self.label._on_change_labelary() self.assertTrue(self.label.labelary_image) diff --git a/printer_zpl2/tests/test_wizard_print_record_label.py b/printer_zpl2/tests/test_wizard_print_record_label.py index 19ce42c..2c2308d 100644 --- a/printer_zpl2/tests/test_wizard_print_record_label.py +++ b/printer_zpl2/tests/test_wizard_print_record_label.py @@ -48,9 +48,9 @@ class TestWizardPrintRecordLabel(TransactionCase): self.assertEqual(wizard.printer_id, self.printer) self.assertEqual(wizard.label_id, self.label) wizard.print_label() - file_name = 'test.zpl' + file_name = "test.zpl" cups.Connection().printFile.assert_called_once_with( - self.printer.system_name, file_name, file_name, options={} + self.printer.system_name, file_name, file_name, options={} ) def test_wizard_multiple_printers_and_labels(self): diff --git a/printer_zpl2/views/printing_label_zpl2.xml b/printer_zpl2/views/printing_label_zpl2.xml index 71f7e60..d99917d 100644 --- a/printer_zpl2/views/printing_label_zpl2.xml +++ b/printer_zpl2/views/printing_label_zpl2.xml @@ -1,4 +1,4 @@ - + - + - - - - - - - - - + + + + + + + + + - + - - - - + + + + - + - - - - - + + + + + - + - - - - + + + +
- - + +

Note : It is an emulation from http://labelary.com/, the result on printer can be different.

@@ -148,21 +258,42 @@ - - + + - - - - - + + + + + - + @@ -174,9 +305,13 @@ printing.label.zpl2 - - - + + + @@ -185,21 +320,32 @@ ir.actions.act_window printing.label.zpl2 tree,form - + [] {} - - - + + + form - + - - - + + + tree - + - +
diff --git a/printer_zpl2/wizard/print_record_label.xml b/printer_zpl2/wizard/print_record_label.xml index f111f2c..c4fafaf 100644 --- a/printer_zpl2/wizard/print_record_label.xml +++ b/printer_zpl2/wizard/print_record_label.xml @@ -1,4 +1,4 @@ - +