From f3be3b151c3281a48a4040387e1cd6f6b15fcfc2 Mon Sep 17 00:00:00 2001 From: qgigon Date: Thu, 5 Dec 2019 15:33:05 +0100 Subject: [PATCH] Added custom options module for printers by report corrections for travis tests fix for flake8 errors and travis tests added new sections and rapleced maintainers added USAGE section in readme + bug corection for update of printers changed copyrights updated manifest and fix for case of false ppd --- printer_custom_options/README.rst | 67 +++ printer_custom_options/__init__.py | 4 + printer_custom_options/__manifest__.py | 27 ++ printer_custom_options/models/__init__.py | 7 + .../models/printer_option.py | 18 + .../models/printer_option_choice.py | 33 ++ .../models/printing_printer.py | 116 +++++ printer_custom_options/models/report_xml.py | 16 + .../readme/CONTRIBUTORS.rst | 2 + printer_custom_options/readme/DESCRIPTION.rst | 2 + printer_custom_options/readme/USAGE.rst | 5 + .../security/ir.model.access.csv | 4 + .../static/description/index.html | 418 ++++++++++++++++++ printer_custom_options/tests/__init__.py | 4 + .../tests/test_printing_printer.py | 105 +++++ .../views/ir_actions_reports_xml_view.xml | 15 + .../views/printing_printer.xml | 19 + 17 files changed, 862 insertions(+) create mode 100755 printer_custom_options/README.rst create mode 100755 printer_custom_options/__init__.py create mode 100755 printer_custom_options/__manifest__.py create mode 100755 printer_custom_options/models/__init__.py create mode 100755 printer_custom_options/models/printer_option.py create mode 100755 printer_custom_options/models/printer_option_choice.py create mode 100755 printer_custom_options/models/printing_printer.py create mode 100755 printer_custom_options/models/report_xml.py create mode 100755 printer_custom_options/readme/CONTRIBUTORS.rst create mode 100755 printer_custom_options/readme/DESCRIPTION.rst create mode 100644 printer_custom_options/readme/USAGE.rst create mode 100755 printer_custom_options/security/ir.model.access.csv create mode 100755 printer_custom_options/static/description/index.html create mode 100755 printer_custom_options/tests/__init__.py create mode 100755 printer_custom_options/tests/test_printing_printer.py create mode 100755 printer_custom_options/views/ir_actions_reports_xml_view.xml create mode 100755 printer_custom_options/views/printing_printer.xml diff --git a/printer_custom_options/README.rst b/printer_custom_options/README.rst new file mode 100755 index 0000000..8ee23f4 --- /dev/null +++ b/printer_custom_options/README.rst @@ -0,0 +1,67 @@ +========================================== +Report to printer - Custom Printer Options +========================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-compassionCH%2Fcompassion--modules-lightgray.png?logo=github + :target: https://github.com/compassionCH/compassion-modules/tree/11.0/printer_custom_options + :alt: compassionCH/compassion-modules + +|badge1| |badge2| |badge3| + +Allow to retrieve a subset of printer options using CUPS, +define which should be used depending on the report and finally set the correct options on the print jobs. + +**Table of contents** + +.. contents:: + :local: + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Compassion CH + +Contributors +~~~~~~~~~~~~ + +Nicolas Bornand +Quentin Gigon + +Maintainers +~~~~~~~~~~~ + +.. 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_custom_options/__init__.py b/printer_custom_options/__init__.py new file mode 100755 index 0000000..cf285ea --- /dev/null +++ b/printer_custom_options/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/printer_custom_options/__manifest__.py b/printer_custom_options/__manifest__.py new file mode 100755 index 0000000..c4f949b --- /dev/null +++ b/printer_custom_options/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# pylint: disable=C8101 +{ + 'name': 'Report to printer - Custom Printer Options', + 'version': '11.0.0.0.0', + 'category': 'Printer', + 'author': 'Compassion CH, OCA', + 'website': 'https://github.com/OCA/report-print-send', + 'license': 'AGPL-3', + 'depends': [ + 'base_report_to_printer' # oca_addons/report-print-send + ], + 'data': [ + 'views/printing_printer.xml', + 'views/ir_actions_reports_xml_view.xml', + 'security/ir.model.access.csv' + ], + 'external_dependencies': { + 'python': ['cups'], + }, + 'installable': True, + 'application': False, + 'summary': "Adds the ability to check all printing options available " + "from a CUPS printer set them in the reports", +} diff --git a/printer_custom_options/models/__init__.py b/printer_custom_options/models/__init__.py new file mode 100755 index 0000000..df779f5 --- /dev/null +++ b/printer_custom_options/models/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import printing_printer +from . import printer_option +from . import printer_option_choice +from . import report_xml diff --git a/printer_custom_options/models/printer_option.py b/printer_custom_options/models/printer_option.py new file mode 100755 index 0000000..314b058 --- /dev/null +++ b/printer_custom_options/models/printer_option.py @@ -0,0 +1,18 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PrinterOption(models.Model): + _name = 'printer.option' + _description = 'Printer Option' + _rec_name = 'option_key' + + option_key = fields.Char(required=True, readonly=True) + printer_id = fields.Many2one( + comodel_name='printing.printer', + string='Printer', + required=True, + readonly=True, + ondelete='cascade', + ) diff --git a/printer_custom_options/models/printer_option_choice.py b/printer_custom_options/models/printer_option_choice.py new file mode 100755 index 0000000..663a677 --- /dev/null +++ b/printer_custom_options/models/printer_option_choice.py @@ -0,0 +1,33 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class PrinterOptionChoice(models.Model): + _name = 'printer.option.choice' + _description = 'Printer Option Choice' + _rec_name = 'composite_key' + + option_key = fields.Char(required=True, readonly=True) + option_value = fields.Char(required=True, readonly=True) + composite_key = fields.Char(compute='_compute_composite_key', store=True) + printer_id = fields.Many2one( + comodel_name='printing.printer', + string='Printer', + required=True, + readonly=True, + ondelete='cascade', + ) + + @api.multi + @api.depends("option_key", "option_value") + def _compute_composite_key(self): + """ Composite key for a printing option key-value pair.""" + for option in self: + option.composite_key = self.build_composite_key(option.option_key, + option.option_value + ) + + @api.model + def build_composite_key(self, option_key, option_value): + return option_key + ':' + option_value diff --git a/printer_custom_options/models/printing_printer.py b/printer_custom_options/models/printing_printer.py new file mode 100755 index 0000000..e0ba5fb --- /dev/null +++ b/printer_custom_options/models/printing_printer.py @@ -0,0 +1,116 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import errno +import logging +import os + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + +try: + import cups +except ImportError: + _logger.debug('Cannot `import cups`.') + + +class PrintingPrinter(models.Model): + _inherit = 'printing.printer' + + printer_options = fields.One2many( + comodel_name='printer.option', + inverse_name='printer_id', + readonly=True) + printer_option_choices = fields.One2many( + comodel_name='printer.option.choice', + inverse_name='printer_id', + string='Option Choices', + readonly=True) + + def _get_cups_ppd(self, cups_connection, cups_printer): + """ Returns a PostScript Printer Description (PPD) file for the + printer. """ + printer_uri = cups_printer['printer-uri-supported'] + printer_system_name = printer_uri[printer_uri.rfind('/') + 1:] + ppd_info = cups_connection.getPPD3(printer_system_name) + ppd_path = ppd_info[2] + if not ppd_path: + return False, False + return ppd_path, cups.PPD(ppd_path) + + def _cleanup_ppd(self, ppd_path): + try: + os.unlink(ppd_path) + except OSError as err: + # ENOENT means No such file or directory + # The file has already been deleted, we can continue the update + if err.errno != errno.ENOENT: + raise + + @api.multi + def _prepare_update_from_cups(self, cups_connection, cups_printer): + vals = super()._prepare_update_from_cups( + cups_connection, cups_printer) + + current_option_keys = self.printer_option_choices.mapped( + 'composite_key') + ppd_path, ppd = self._get_cups_ppd(cups_connection, cups_printer) + if ppd_path and ppd: + self._load_printer_option_list(ppd) + + new_option_values = [] + for printer_option in self.printer_options: + option_key = printer_option.option_key + new_options = self.discover_values_of_option(ppd, + current_option_keys, + option_key) + new_option_values.extend(new_options) + vals['printer_option_choices'] = new_option_values + + self._cleanup_ppd(ppd_path) + return vals + + def _load_printer_option_list(self, ppd): + if len(self.printer_options) > 0 or not self: + # Only fetch options the first time or + # if the printer is already in the system. + return + option_inserts = [] + for option_group in ppd.optionGroups: + for option in option_group.options: + option_inserts.append((0, 0, {'option_key': option.keyword})) + self.printer_options = option_inserts + + def discover_values_of_option(self, ppd, current_option_keys, option_key): + """ Returns all new values for one printer option category. Most + probably it will insert all option values the first time we sync with + CUPS and then return an empty list.""" + option = ppd.findOption(option_key) + if not option: + return [] + + printer_option_values = {entry['choice'] for entry in option.choices} + option_model = self.env['printer.option.choice'] + + # Insertion tuples + return [ + (0, 0, + {'option_value': option_value, 'option_key': option_key}) + for option_value in printer_option_values + if option_model.build_composite_key(option_key, option_value) + not in current_option_keys + ] + + @api.multi + def print_options(self, report=None, **print_opts): + # Use lpoptions to have an exhaustive list of the supported options + options = super().print_options(report=report, **print_opts) + + if report is not None: + # Some modules pass report_name instead of the report. + full_report = self.env['report']._get_report_from_name(report) \ + if isinstance(report, str) else report + for printer_option in full_report.printer_options: + options[ + printer_option.option_key] = printer_option.option_value + return options diff --git a/printer_custom_options/models/report_xml.py b/printer_custom_options/models/report_xml.py new file mode 100755 index 0000000..8f9bfac --- /dev/null +++ b/printer_custom_options/models/report_xml.py @@ -0,0 +1,16 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class IrActionsReportXml(models.Model): + _inherit = 'ir.actions.report' + + printer_options = fields.Many2many('printer.option.choice', + string='Printer Options') + + @api.multi + @api.onchange('printing_printer_id') + def on_change_printer(self): + for report in self: + report.printer_options = False diff --git a/printer_custom_options/readme/CONTRIBUTORS.rst b/printer_custom_options/readme/CONTRIBUTORS.rst new file mode 100755 index 0000000..2a2e7cf --- /dev/null +++ b/printer_custom_options/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +Nicolas Bornand +Quentin Gigon diff --git a/printer_custom_options/readme/DESCRIPTION.rst b/printer_custom_options/readme/DESCRIPTION.rst new file mode 100755 index 0000000..c1ab365 --- /dev/null +++ b/printer_custom_options/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +Allow to retrieve a subset of printer options using CUPS, +define which should be used depending on the report and finally set the correct options on the print jobs. diff --git a/printer_custom_options/readme/USAGE.rst b/printer_custom_options/readme/USAGE.rst new file mode 100644 index 0000000..0daa7c2 --- /dev/null +++ b/printer_custom_options/readme/USAGE.rst @@ -0,0 +1,5 @@ +After installing update the CUPS printers in Settings > Printing > Update Printers from CUPS +Usage +In the printer you can see all se options Settings > Printing > Printers +If you want print a report with specific options, you can change these in Settings > Printing > Reports. +When no option is configured for a report, the default option setup on the CUPS server is used. diff --git a/printer_custom_options/security/ir.model.access.csv b/printer_custom_options/security/ir.model.access.csv new file mode 100755 index 0000000..a5a60c9 --- /dev/null +++ b/printer_custom_options/security/ir.model.access.csv @@ -0,0 +1,4 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_printer_option_choice_all,printer_option_choice all,model_printer_option_choice,base_report_to_printer.printing_group_user,1,0,0,0 +access_printer_option_choice_operator,printer_option_choice operator,model_printer_option_choice,base_report_to_printer.printing_group_manager,1,1,1,1 +access_printer_option,access_printer_option,model_printer_option,base.group_user,1,0,0,0 diff --git a/printer_custom_options/static/description/index.html b/printer_custom_options/static/description/index.html new file mode 100755 index 0000000..da48623 --- /dev/null +++ b/printer_custom_options/static/description/index.html @@ -0,0 +1,418 @@ + + + + + + +Report to printer - Custom Printer Options + + + +
+

Report to printer - Custom Printer Options

+ + +

Beta License: AGPL-3 compassionCH/compassion-modules

+

Allow to retrieve a subset of printer options using CUPS, +define which should be used depending on the report and finally set the correct options on the print jobs.

+

Table of contents

+ +
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Compassion CH
  • +
+
+
+

Contributors

+

Nicolas Bornand +Quentin Gigon <gigon.quentin@gmail.com>

+
+
+

Maintainers

+

This module is maintained by Compassion Switzerland.

+Compassion Switzerland +

Compassion Switzerland is a nonprofit organization whose +mission is to release children from extreme poverty in Jesus name.

+

This module is part of the compassionCH/compassion-modules project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/printer_custom_options/tests/__init__.py b/printer_custom_options/tests/__init__.py new file mode 100755 index 0000000..0c7b038 --- /dev/null +++ b/printer_custom_options/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_printing_printer diff --git a/printer_custom_options/tests/test_printing_printer.py b/printer_custom_options/tests/test_printing_printer.py new file mode 100755 index 0000000..bad3584 --- /dev/null +++ b/printer_custom_options/tests/test_printing_printer.py @@ -0,0 +1,105 @@ +# Copyright 2019 Compassion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import mock +import types +from odoo.tests.common import TransactionCase + +server_model = 'odoo.addons.base_report_to_printer.models.printing_server' + + +class TestPrintingPrinter(TransactionCase): + + def setUp(self): + super().setUp() + self.server = self.env['printing.server'].create({}) + self.ServerModel = self.env['printing.server'] + 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.bin_option = self.env['printer.option.choice'].create({ + 'option_key': 'OutputBin', + 'option_value': 'bin1', + 'printer_id': self.printer.id, + }) + self.printer_vals = { + 'printer-info': 'Info', + 'printer-make-and-model': 'Make and Model', + 'printer-location': "location", + 'device-uri': 'URI', + 'printer-uri-supported': '/' + self.printer.system_name + } + + def test_print_options__copies_options_from_report(self): + mock_report = mock.Mock() + mock_report.printer_options = [self.bin_option] + + options = self.printer \ + .print_options(report=mock_report) + + self.assertEqual(options['OutputBin'], 'bin1') + + def test_print_options__without_option(self): + mock_report = mock.Mock() + mock_report.printer_options = [] + + options = self.printer \ + .print_options(report=mock_report) + + self.assertEqual(options, {}) + + @mock.patch('%s.cups' % server_model) + def test_prepare_update_from_cups__load_options(self, patched_cups): + patched_cups.Connection.return_value.getPPD3.return_value = (200, 1, None) + self._mock_cups_options(self.printer, []) + + self.printer._prepare_update_from_cups( + patched_cups.Connection(), self.printer_vals) + + self.assertEqual(len(self.printer.printer_options), 1) + self.assertEqual(self.printer.printer_options[0].option_key, + 'OutputBin') + + @mock.patch('%s.cups' % server_model) + def test_prepare_update_from_cups(self, patched_cups): + patched_cups.Connection.return_value.getPPD3.return_value = (200, 1, None) + self._mock_cups_options(self.printer, + [{'choice': 'bin1'}, + {'choice': 'bin2'}]) + + vals = self.printer._prepare_update_from_cups( + patched_cups.Connection(), self.printer_vals) + + # OutputBin:bin1 was already inserted + self.assertEqual(len(vals['printer_option_choices']), 1) + self.assertIn((0, 0, + {'option_key': 'OutputBin', 'option_value': 'bin2'}), + vals['printer_option_choices']) + self.assertNotIn((0, 0, + {'option_key': 'OutputBin', 'option_value': 'bin1'}), + vals['printer_option_choices']) + + def _mock_cups_options(self, printer, + choices): + def mock__get_cups_ppd(*args): + mock_ppd = mock.Mock() + # Mock optionGroups + mock_option_group = mock.Mock() + mock_option = mock.Mock() + mock_option.keyword = 'OutputBin' + mock_option.choices = choices + mock_option_group.options = [mock_option] + mock_ppd.optionGroups = [mock_option_group] + # Mock findOption + mock_ppd.findOption = lambda x: mock_option + return 'pdd_path', mock_ppd + + printer._get_cups_ppd = \ + types.MethodType(mock__get_cups_ppd, self.printer) diff --git a/printer_custom_options/views/ir_actions_reports_xml_view.xml b/printer_custom_options/views/ir_actions_reports_xml_view.xml new file mode 100755 index 0000000..979a1a9 --- /dev/null +++ b/printer_custom_options/views/ir_actions_reports_xml_view.xml @@ -0,0 +1,15 @@ + + + + ir.actions.report.printing_options + ir.actions.report + + + + + + + + diff --git a/printer_custom_options/views/printing_printer.xml b/printer_custom_options/views/printing_printer.xml new file mode 100755 index 0000000..c762893 --- /dev/null +++ b/printer_custom_options/views/printing_printer.xml @@ -0,0 +1,19 @@ + + + + + printing.printer.form + printing.printer + + + + + + + + + + + +