diff --git a/base_report_to_label_printer/tests/test_ir_actions_report.py b/base_report_to_label_printer/tests/test_ir_actions_report.py index 5203bc1d43b..82b3a8901ca 100644 --- a/base_report_to_label_printer/tests/test_ir_actions_report.py +++ b/base_report_to_label_printer/tests/test_ir_actions_report.py @@ -1,7 +1,6 @@ # Copyright (C) 2022 Raumschmiede GmbH - Christopher Hansen () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import logging from odoo.addons.base.tests.common import BaseCommon @@ -15,13 +14,11 @@ def setUpClass(cls): cls.Model = cls.env["ir.actions.report"].with_context( skip_printer_exception=True ) - cls.server = cls.env["printing.server"].create({}) def new_printer(self): return self.env["printing.printer"].create( { "name": "Printer", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -38,16 +35,11 @@ def test_print_behavior_user_label_printer(self): report.label = True self.env.user.printing_action = "client" self.env.user.default_label_printer_id = self.new_printer() - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - self.assertEqual( - report.behaviour(), - { - "action": "client", - "printer": self.env.user.default_label_printer_id, - "tray": False, - }, - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) + self.assertEqual( + report.behaviour(), + { + "action": "client", + "printer": self.env.user.default_label_printer_id, + "tray": False, + }, + ) diff --git a/base_report_to_printer/README.rst b/base_report_to_printer/README.rst index 687bd6f04c8..9000b2b01ed 100644 --- a/base_report_to_printer/README.rst +++ b/base_report_to_printer/README.rst @@ -11,7 +11,7 @@ Report to printer !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:411351c67395d3219ea49f07addba2966080696f3b776d899712288e24f98fc2 + !! source digest: sha256:e5af916528d668b5628606b5b2216ec40d3f05f183d01eb157311e01719dcb49 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -32,33 +32,32 @@ Report to printer |badge1| |badge2| |badge3| |badge4| |badge5| -This module allows users to send reports to a printer attached to the -server. +This module provides the core framework to send Odoo reports directly to +printers. It defines the base models, configuration options and printing +workflow, without depending on a specific printing protocol. -It adds an optional behaviour on reports to send it directly to a -printer. +The actual connection with printers is delegated to extension modules +(e.g. base_report_to_printer_cups), which implement support for a given +printing backend. -- Send to Client is the default behaviour providing you a downloadable - PDF -- Send to Printer prints the report on selected printer +Key features: -It detects trays on printers installation plus permits to select the -paper source on which you want to print directly. +Flexible report output behavior: -Report behaviour is defined by settings. +Send to Client (default): generates a downloadable PDF. -You will find this option on default user config, on default report -config and on specific config per user per report. +Send to Printer: sends the report directly to a configured printer (via +backend module). -This allows you to dedicate a specific paper source for example for -preprinted paper such as payment slip. +Support for user-level, report-level, and combined user/report printing +rules. -Settings can be configured: +Extensible design: new modules can add support for additional printing +systems or protocols. -- globally -- per user -- per report -- per user and report +This modular approach allows administrators to configure printing +globally, per user, per report, or per user/report combination, while +keeping the printing backend independent and replaceable. **Table of contents** @@ -68,64 +67,138 @@ Settings can be configured: Installation ============ -To install this module, you need to: -1. Install PyCups - https://pypi.python.org/pypi/pycups - -.. code:: bash - - sudo apt-get install cups - sudo apt-get install libcups2-dev - sudo apt-get install python3-dev - sudo pip install pycups Configuration ============= -To configure this module, you need to: +Configuration Guide + +To configure and start using the Base Report to Printer module, follow +these steps: + +1. User Access Rights + +Go to Settings → Users & Companies → Users. + +Open the user form and in the Access Rights tab, enable: + +Printing / Print User → grants access to the printing menu and +user-specific printing preferences. + +2. Global Printing Settings + +Navigate to Settings → Technical → Printing → Printing Settings. + +Define the default printing behavior for reports: + +Send to Client (default): generates a downloadable PDF. + +Send to Printer: sends the report directly to a configured printer +(requires a backend module such as base_report_to_printer_cups). + +3. User Preferences + +Each user can configure their own printing behavior: + +Go to Preferences (top-right menu, click on your name). + +In the Printing section, choose: + +Default action (Send to Client / Send to Printer). + +Preferred printer (if a backend module is installed and printers are +detected). + +4. Report-Specific Configuration + +Go to Settings → Technical → Reports → Reports. + +For each report, you can define: -1. Enable the "Printing / Print User" option under access rights to give - users the ability to view the print menu. +Default printing action. -The jobs will be sent to the printer with a name matching the -print_report_name of the report (truncated at 80 characters). By default -this will not be displayed by CUPS web interface or in Odoo. To see this -information, you need to change the configuration of your CUPS server -and set the JobPrivateValue directive to "none" (or some other list of -values which does not include "job-name") , and reload the server. See -cupsd.conf(5) -<`https://www.cups.org/doc/man-cupsd.conf.html\\> >`__ -for details. +Default printer (if available). + +These settings can be overridden at the user level. + +5. Per User & Report Combination + +Navigate to Settings → Technical → Printing → Report Configurations. + +Here you can assign specific rules combining: + +A user. + +A report. + +A printing action (Send to Client / Send to Printer). + +A printer and tray (if supported by the backend). + +6. Installing a Backend (e.g., CUPS) + +The base module does not include any printing backend. To connect with +actual printers you must install an extension module, such as: + +base_report_to_printer_cups → adds support for CUPS printers, trays, and +job management. + +Once installed, printers from the backend will be available in the +configuration menus above. Usage ===== -Guidelines for use: +Usage +===== - - To update the CUPS printers in *Settings > Printing > Update - Printers from CUPS* - - To print a report on a specific printer or tray, you can change - these in *Settings > Printing > Reports* to define default - behaviour. - - To print a report on a specific printer and/or tray for a user, you - can change these in *Settings > Printing > Reports* in *Specific - actions per user* - - Users may also select a default action, printer or tray in their - preferences. +Guidelines for use: -When no tray is configured for a report and a user, the default tray -setup on the CUPS server is used. +- To use a specific printing backend (e.g. CUPS), make sure the + corresponding module (such as ``base_report_to_printer_cups``) is + installed and configured. +- To print a report on a specific printer or tray, you can configure + defaults in *Settings > Printing > Reports*. +- To define user-specific behaviour, go to *Settings > Printing > + Reports* and configure *Specific actions per user*. +- Each user can also select a default action, printer or tray in their + *Preferences*. +- When no tray is configured for a report or a user, the default tray + defined by the printing backend (e.g. the CUPS server) will be used. + +Notes +----- + +- This module (``base_report_to_printer``) only provides the **base + framework**. +- To connect with a real print system, you must install an additional + backend module (e.g. ``base_report_to_printer_cups`` for CUPS). +- Other backend modules can be developed to support different print + protocols or environments. Known issues / Roadmap ====================== -- With threaded printing there's no download fallback when the issue - isn't detected by the CUPS Odoo backend. To able to do it, we would - need to notify the bus or use web_notify for it. + Changelog ========= +18.0.1.0.0 (2025-10-15) +----------------------- + +- [REFAC] Extracted all CUPS-specific functionality into a dedicated + module: ``base_report_to_printer_cups``. +- [ADD] Introduced a base abstraction layer for report-to-printer, to + allow adding new backends (protocols) without modifying the core + module. +- [IMP] Improved configuration instructions (global, per-user, + per-report, and per user+report). +- [CLEAN] Updated documentation and module description to reflect new + architecture. +- + 13.0.1.0.0 (2019-09-30) ----------------------- @@ -180,12 +253,13 @@ Contributors - Akim Juillerat - Jacques-Etienne Baudoux (BCIM) - Tris Doan +- Miquel Alzanillas Other credits ------------- -The migration of this module from 17.0 to 18.0 was financially supported -by Camptocamp. +The migration of the original module (base_report_to_printer) from 17.0 +to 18.0 was financially supported by Camptocamp. Maintainers ----------- diff --git a/base_report_to_printer/__manifest__.py b/base_report_to_printer/__manifest__.py index aafbd0057c1..24721c3f222 100644 --- a/base_report_to_printer/__manifest__.py +++ b/base_report_to_printer/__manifest__.py @@ -18,15 +18,12 @@ "data": [ "data/printing_data.xml", "security/security.xml", - "security/ir.model.access.csv", "views/printing_printer.xml", - "views/printing_server.xml", "views/printing_job.xml", "views/printing_report.xml", - "views/res_users.xml", "views/ir_actions_report.xml", + "views/res_users.xml", "wizards/print_attachment_report.xml", - "wizards/printing_printer_update_wizard_view.xml", ], "assets": { "web.assets_backend": [ @@ -35,5 +32,4 @@ }, "installable": True, "application": False, - "external_dependencies": {"python": ["pycups"]}, } diff --git a/base_report_to_printer/data/neutralize.sql b/base_report_to_printer/data/neutralize.sql index 257602f1da1..f461c55fa06 100644 --- a/base_report_to_printer/data/neutralize.sql +++ b/base_report_to_printer/data/neutralize.sql @@ -1,2 +1 @@ -update printing_server set active=false; update printing_printer set active=false; diff --git a/base_report_to_printer/data/printing_data.xml b/base_report_to_printer/data/printing_data.xml index f9304756ec0..b8d629c8397 100644 --- a/base_report_to_printer/data/printing_data.xml +++ b/base_report_to_printer/data/printing_data.xml @@ -9,16 +9,6 @@ Send to Client client - - Update Printers Jobs - - - 1 - minutes - - code - model.action_update_jobs() - ) # Copyright 2024 Tecnativa - Sergio Teruel # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging import threading from time import time from odoo import _, api, exceptions, fields, models, registry from odoo.tools.safe_eval import safe_eval +_logger = logging.getLogger(__name__) + REPORT_TYPES = {"qweb-pdf": "pdf", "qweb-text": "text"} @@ -153,6 +156,9 @@ def print_document(self, record_ids, data=None): """Print a document, do not return the document file""" report_type = REPORT_TYPES.get(self.report_type) if not report_type: + _logger.warning( + "Report type %s not supported for direct printing", self.report_type + ) raise exceptions.UserError( _("This report type (%s) is not supported by direct printing!") % str(self.report_type) diff --git a/base_report_to_printer/models/printing_job.py b/base_report_to_printer/models/printing_job.py index d38fc2c0b07..1445fa531bc 100644 --- a/base_report_to_printer/models/printing_job.py +++ b/base_report_to_printer/models/printing_job.py @@ -10,23 +10,9 @@ class PrintingJob(models.Model): _name = "printing.job" - _description = "Printing Job" - _order = "job_id_cups DESC" + _description = "Print Job" name = fields.Char(help="Job name.") - active = fields.Boolean( - default=True, help="Unchecked if the job is purged from CUPS." - ) - job_id_cups = fields.Integer( - string="Job ID", required=True, help="CUPS id for this job." - ) - server_id = fields.Many2one( - comodel_name="printing.server", - string="Server", - related="printer_id.server_id", - store=True, - help="Server which hosts this job.", - ) printer_id = fields.Many2one( comodel_name="printing.printer", string="Printer", @@ -34,11 +20,6 @@ class PrintingJob(models.Model): ondelete="cascade", help="Printer used for this job.", ) - job_media_progress = fields.Integer( - string="Media Progress", - required=True, - help="Percentage of progress for this job.", - ) time_at_creation = fields.Datetime( string="Creation Date", required=True, @@ -53,79 +34,16 @@ class PrintingJob(models.Model): job_state = fields.Selection( selection=[ ("pending", "Pending"), - ("pending held", "Pending Held"), - ("processing", "Processing"), - ("processing stopped", "Processing Stopped"), - ("canceled", "Canceled"), - ("aborted", "Aborted"), ("completed", "Completed"), ("unknown", "Unknown"), ], string="State", help="Current state of the job.", ) - job_state_reason = fields.Selection( - selection=[ - ("none", "No reason"), - ("aborted-by-system", "Aborted by the system"), - ("compression-error", "Error in the compressed data"), - ("cups-filter-crashed", "CUPS filter crashed"), - ("document-access-error", "The URI cannot be accessed"), - ("document-format-error", "Error in the document"), - ("job-canceled-at-device", "Cancelled at the device"), - ("job-canceled-by-operator", "Cancelled by the printer operator"), - ("job-canceled-by-user", "Cancelled by the user"), - ("job-completed-successfully", "Completed successfully"), - ("job-completed-with-errors", "Completed with some errors"), - ("job-completed-with-warnings", "Completed with some warnings"), - ("job-data-insufficient", "No data has been received"), - ("job-hold-until-specified", "Currently held"), - ("job-incoming", "Files are currently being received"), - ("job-interpreting", "Currently being interpreted"), - ("job-outgoing", "Currently being sent to the printer"), - ("job-printing", "Currently printing"), - ("job-queued", "Queued for printing"), - ("job-queued-for-marker", "Printer needs ink/marker/toner"), - ("job-restartable", "Can be restarted"), - ("job-transforming", "Being transformed into a different format"), - ("printer-stopped", "Printer is stopped"), - ("printer-stopped-partly", "Printer state reason set to 'stopped-partly'"), - ( - "processing-to-stop-point", - "Cancelled, but printing already processed pages", - ), - ("queued-in-device", "Queued at the output device"), - ("resources-are-not-ready", "Resources not available to print the job"), - ("service-off-line", "Held because the printer is offline"), - ("submission-interrupted", "Files were not received in full"), - ("unsupported-compression", "Compressed using an unknown algorithm"), - ("unsupported-document-format", "Unsupported format"), - ], - string="State Reason", - help="Reason for the current job state.", - ) - - _sql_constraints = [ - ( - "job_id_cups_unique", - "UNIQUE(job_id_cups, server_id)", - "The id of the job must be unique per server !", - ) - ] def action_cancel(self): self.ensure_one() return self.cancel() - def cancel(self, purge_job=False): - for job in self: - connection = job.server_id._open_connection() - if not connection: - continue - - connection.cancelJob(job.job_id_cups, purge_job=purge_job) - - # Update jobs' states info Odoo - self.mapped("server_id").update_jobs(which="all", first_job_id=job.job_id_cups) - + def cancel(self): return True diff --git a/base_report_to_printer/models/printing_printer.py b/base_report_to_printer/models/printing_printer.py index ac312866d1c..f01cfa7ffb8 100644 --- a/base_report_to_printer/models/printing_printer.py +++ b/base_report_to_printer/models/printing_printer.py @@ -7,45 +7,27 @@ # Copyright (C) 2023 Jacques-Etienne Baudoux (BCIM) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import errno import logging import os from tempfile import mkstemp -from odoo import api, fields, models +from odoo import _, exceptions, fields, models _logger = logging.getLogger(__name__) -try: - import cups -except ImportError: - _logger.debug("Cannot `import cups`.") - class PrintingPrinter(models.Model): - """ - Printers - """ - _name = "printing.printer" - _description = "Printer" - _order = "name" + _description = "Logical Printer" - name = fields.Char(required=True, index=True) - active = fields.Boolean(default=True) - server_id = fields.Many2one( - comodel_name="printing.server", - string="Server", + name = fields.Char(required=True) + system_name = fields.Char(required=True) + backend = fields.Selection( + selection=[("base", "Base")], required=True, - help="Server used to access this printer.", + default="base", ) - job_ids = fields.One2many( - comodel_name="printing.job", - inverse_name="printer_id", - string="Jobs", - help="Jobs printed on this printer.", - ) - system_name = fields.Char(required=True, index=True) + active = fields.Boolean(default=True) default = fields.Boolean(readonly=True) status = fields.Selection( selection=[ @@ -64,113 +46,22 @@ class PrintingPrinter(models.Model): model = fields.Char(readonly=True) location = fields.Char(readonly=True) uri = fields.Char(string="URI", readonly=True) - tray_ids = fields.One2many( - comodel_name="printing.tray", inverse_name="printer_id", string="Paper Sources" - ) - multi_thread = fields.Boolean( - compute="_compute_multi_thread", readonly=False, store=True - ) - - @api.depends("server_id.multi_thread") - def _compute_multi_thread(self): - for printer in self: - printer.multi_thread = printer.server_id.multi_thread - - def _prepare_update_from_cups(self, cups_connection, cups_printer): - mapping = {3: "available", 4: "printing", 5: "error"} - cups_vals = { - "name": self.name or cups_printer["printer-info"], - "model": cups_printer.get("printer-make-and-model", False), - "location": cups_printer.get("printer-location", False), - "uri": cups_printer.get("device-uri", False), - "status": mapping.get(cups_printer.get("printer-state"), "unknown"), - "status_message": cups_printer.get("printer-state-message", ""), - } - - # prevent write if the field didn't change - vals = { - fieldname: value - for fieldname, value in cups_vals.items() - if not self or value != self[fieldname] - } - - 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 vals - - ppd = cups.PPD(ppd_path) - option = ppd.findOption("InputSlot") - 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 - if not option: - return vals - - tray_commands = [] - cups_trays = { - tray_option["choice"]: tray_option["text"] for tray_option in option.choices - } - - # Add new trays - tray_commands.extend( - [ - (0, 0, {"name": text, "system_name": choice}) - for choice, text in cups_trays.items() - if choice not in self.tray_ids.mapped("system_name") - ] - ) - - # Remove deleted trays - tray_commands.extend( - [ - (2, tray.id) - for tray in self.tray_ids.filtered( - lambda record: record.system_name not in cups_trays.keys() - ) - ] - ) - if tray_commands: - vals["tray_ids"] = tray_commands - return vals - - def print_document(self, report, content, **print_opts): - """Print a file - Format could be pdf, qweb-pdf, raw, ... - """ - self.ensure_one() - fd, file_name = mkstemp() - if isinstance(content, str): - content = content.encode() - try: - os.write(fd, content) - finally: - os.close(fd) - - return self.print_file(file_name, report=report, **print_opts) @staticmethod def _set_option_doc_format(report, value): return {"raw": "True"} if value == "raw" else {} - # Backwards compatibility of builtin used as kwarg _set_option_format = _set_option_doc_format + @staticmethod + def _set_option_noop(report, value): + return {} + def _set_option_tray(self, report, value): """Note we use self here as some older PPD use tray rather than InputSlot so we may need to query printer in override""" return {"InputSlot": str(value)} if value else {} - @staticmethod - def _set_option_noop(report, value): - return {} - _set_option_action = _set_option_noop _set_option_printer = _set_option_noop @@ -183,24 +74,51 @@ def print_options(self, report=None, **print_opts): options[option] = str(value) return options - def print_file(self, file_name, report=None, **print_opts): - """Print a file""" + def print_document( + self, report, content, action=None, doc_format="qweb-pdf", **kwargs + ): + """Generic document print logic, backend-agnostic.""" self.ensure_one() - title = print_opts.pop("title", file_name) - connection = self.server_id._open_connection(raise_on_error=True) - options = self.print_options(report=report, **print_opts) + fd, file_name = mkstemp() + if isinstance(content, str): + content = content.encode("utf-8") + try: + os.write(fd, content) + finally: + os.close(fd) + try: + self.print_file(file_name, report=report, **kwargs) + except Exception as e: + raise exceptions.UserError(_("Failed to print document: %s") % e) from e + finally: + try: + os.remove(file_name) + except OSError as err: + _logger.warning(f"Unable to remove temporary file {file_name}: {err}") + return True + def print_file(self, file_name, report=None, **print_opts): _logger.debug( - f"Sending job to CUPS printer {self.system_name} on " - f"{self.server_id.address} with options {options}" + f"Generic print_file() called for {self.name}\ + with file '{file_name}' and options: {print_opts}." ) - connection.printFile(self.system_name, file_name, title, options=options) - _logger.info(f"Printing job: '{file_name}' on {self.server_id.address}") - try: - os.remove(file_name) - except OSError as exc: - _logger.warning(f"Unable to remove temporary file {file_name}: {exc}") - return True + return False + + def print_test_page(self): + _logger.debug(f"Generic print_test_page() called for {self.name}.") + return False + + def cancel_all_jobs(self, purge_jobs=False): + _logger.debug(f"Generic cancel_all_jobs() called for {self.name}.") + return False + + def enable(self): + _logger.debug(f"Generic enable() called for {self.name}.") + return False + + def disable(self): + _logger.debug(f"Generic disable() called for {self.name}.") + return False def set_default(self): if not self: @@ -221,48 +139,3 @@ def get_default(self): def action_cancel_all_jobs(self): self.ensure_one() return self.cancel_all_jobs() - - def cancel_all_jobs(self, purge_jobs=False): - for printer in self: - connection = printer.server_id._open_connection() - connection.cancelAllJobs(name=printer.system_name, purge_jobs=purge_jobs) - - # Update jobs' states into Odoo - self.mapped("server_id").update_jobs(which="completed") - - return True - - def enable(self): - for printer in self: - connection = printer.server_id._open_connection() - connection.enablePrinter(printer.system_name) - - # Update printers' stats into Odoo - self.mapped("server_id").update_printers() - - return True - - def disable(self): - for printer in self: - connection = printer.server_id._open_connection() - connection.disablePrinter(printer.system_name) - - # Update printers' stats into Odoo - self.mapped("server_id").update_printers() - - return True - - def print_test_page(self): - for printer in self: - connection = printer.server_id._open_connection() - if printer.model == "Local Raw Printer": - fd, file_name = mkstemp() - try: - os.write(fd, b"TEST") - finally: - os.close(fd) - connection.printTestPage(printer.system_name, file=file_name) - else: - connection.printTestPage(printer.system_name) - - self.mapped("server_id").update_jobs(which="completed") diff --git a/base_report_to_printer/readme/CONFIGURE.md b/base_report_to_printer/readme/CONFIGURE.md index aab90ffbdfb..7bf67b7f6a1 100644 --- a/base_report_to_printer/readme/CONFIGURE.md +++ b/base_report_to_printer/readme/CONFIGURE.md @@ -1,13 +1,67 @@ -To configure this module, you need to: - -1. Enable the "Printing / Print User" option under access rights to - give users the ability to view the print menu. - -The jobs will be sent to the printer with a name matching the -print_report_name of the report (truncated at 80 characters). By default -this will not be displayed by CUPS web interface or in Odoo. To see this -information, you need to change the configuration of your CUPS server -and set the JobPrivateValue directive to "none" (or some other list of -values which does not include "job-name") , and reload the server. See -cupsd.conf(5) \ for -details. +Configuration Guide + +To configure and start using the Base Report to Printer module, follow these steps: + +1. User Access Rights + +Go to Settings → Users & Companies → Users. + +Open the user form and in the Access Rights tab, enable: + +Printing / Print User → grants access to the printing menu and user-specific printing preferences. + +2. Global Printing Settings + +Navigate to Settings → Technical → Printing → Printing Settings. + +Define the default printing behavior for reports: + +Send to Client (default): generates a downloadable PDF. + +Send to Printer: sends the report directly to a configured printer (requires a backend module such as base_report_to_printer_cups). + +3. User Preferences + +Each user can configure their own printing behavior: + +Go to Preferences (top-right menu, click on your name). + +In the Printing section, choose: + +Default action (Send to Client / Send to Printer). + +Preferred printer (if a backend module is installed and printers are detected). + +4. Report-Specific Configuration + +Go to Settings → Technical → Reports → Reports. + +For each report, you can define: + +Default printing action. + +Default printer (if available). + +These settings can be overridden at the user level. + +5. Per User & Report Combination + +Navigate to Settings → Technical → Printing → Report Configurations. + +Here you can assign specific rules combining: + +A user. + +A report. + +A printing action (Send to Client / Send to Printer). + +A printer and tray (if supported by the backend). + +6. Installing a Backend (e.g., CUPS) + +The base module does not include any printing backend. To connect with actual printers you must install an extension module, such as: + +base_report_to_printer_cups → adds support for CUPS printers, trays, and job management. + +Once installed, printers from the backend will be available in the configuration menus above. diff --git a/base_report_to_printer/readme/CONTRIBUTORS.md b/base_report_to_printer/readme/CONTRIBUTORS.md index 84cef65de94..0f94b9e29fc 100644 --- a/base_report_to_printer/readme/CONTRIBUTORS.md +++ b/base_report_to_printer/readme/CONTRIBUTORS.md @@ -16,3 +16,4 @@ - Akim Juillerat \<\> - Jacques-Etienne Baudoux (BCIM) \<\> - Tris Doan \<\> +- Miquel Alzanillas \<\> diff --git a/base_report_to_printer/readme/CREDITS.md b/base_report_to_printer/readme/CREDITS.md index 83b3ec91f7d..f8ab461ed36 100644 --- a/base_report_to_printer/readme/CREDITS.md +++ b/base_report_to_printer/readme/CREDITS.md @@ -1 +1 @@ -The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp. +The migration of the original module (base_report_to_printer) from 17.0 to 18.0 was financially supported by Camptocamp. diff --git a/base_report_to_printer/readme/DESCRIPTION.md b/base_report_to_printer/readme/DESCRIPTION.md index 93a46b3182c..713279982f6 100644 --- a/base_report_to_printer/readme/DESCRIPTION.md +++ b/base_report_to_printer/readme/DESCRIPTION.md @@ -1,27 +1,18 @@ -This module allows users to send reports to a printer attached to the -server. +This module provides the core framework to send Odoo reports directly to printers. +It defines the base models, configuration options and printing workflow, without depending on a specific printing protocol. -It adds an optional behaviour on reports to send it directly to a -printer. +The actual connection with printers is delegated to extension modules (e.g. base_report_to_printer_cups), which implement support for a given printing backend. -- Send to Client is the default behaviour providing you a downloadable - PDF -- Send to Printer prints the report on selected printer +Key features: -It detects trays on printers installation plus permits to select the -paper source on which you want to print directly. +Flexible report output behavior: -Report behaviour is defined by settings. +Send to Client (default): generates a downloadable PDF. -You will find this option on default user config, on default report -config and on specific config per user per report. +Send to Printer: sends the report directly to a configured printer (via backend module). -This allows you to dedicate a specific paper source for example for -preprinted paper such as payment slip. +Support for user-level, report-level, and combined user/report printing rules. -Settings can be configured: +Extensible design: new modules can add support for additional printing systems or protocols. -- globally -- per user -- per report -- per user and report +This modular approach allows administrators to configure printing globally, per user, per report, or per user/report combination, while keeping the printing backend independent and replaceable. diff --git a/base_report_to_printer/readme/HISTORY.md b/base_report_to_printer/readme/HISTORY.md index d7372fe34b6..3503a101b9d 100644 --- a/base_report_to_printer/readme/HISTORY.md +++ b/base_report_to_printer/readme/HISTORY.md @@ -1,3 +1,11 @@ + +## 18.0.1.0.0 (2025-10-15) + +- [REFAC] Extracted all CUPS-specific functionality into a dedicated module: `base_report_to_printer_cups`. +- [ADD] Introduced a base abstraction layer for report-to-printer, to allow adding new backends (protocols) without modifying the core module. +- [IMP] Improved configuration instructions (global, per-user, per-report, and per user+report). +- [CLEAN] Updated documentation and module description to reflect new architecture. +- ## 13.0.1.0.0 (2019-09-30) - \[RELEASE\] Port from V12. diff --git a/base_report_to_printer/readme/INSTALL.md b/base_report_to_printer/readme/INSTALL.md index 08df9c958d0..e69de29bb2d 100644 --- a/base_report_to_printer/readme/INSTALL.md +++ b/base_report_to_printer/readme/INSTALL.md @@ -1,10 +0,0 @@ -To install this module, you need to: - -1. Install PyCups - - -``` bash -sudo apt-get install cups -sudo apt-get install libcups2-dev -sudo apt-get install python3-dev -sudo pip install pycups -``` diff --git a/base_report_to_printer/readme/ROADMAP.md b/base_report_to_printer/readme/ROADMAP.md index f35d11c3d9a..e69de29bb2d 100644 --- a/base_report_to_printer/readme/ROADMAP.md +++ b/base_report_to_printer/readme/ROADMAP.md @@ -1,3 +0,0 @@ -- With threaded printing there's no download fallback when the issue - isn't detected by the CUPS Odoo backend. To able to do it, we would - need to notify the bus or use web_notify for it. diff --git a/base_report_to_printer/readme/USAGE.md b/base_report_to_printer/readme/USAGE.md index 570c4a162b4..68745d2efa2 100644 --- a/base_report_to_printer/readme/USAGE.md +++ b/base_report_to_printer/readme/USAGE.md @@ -1,15 +1,18 @@ +# Usage + Guidelines for use: -> - To update the CUPS printers in *Settings \> Printing \> Update -> Printers from CUPS* -> - To print a report on a specific printer or tray, you can change -> these in *Settings \> Printing \> Reports* to define default -> behaviour. -> - To print a report on a specific printer and/or tray for a user, you -> can change these in *Settings \> Printing \> Reports* in *Specific -> actions per user* -> - Users may also select a default action, printer or tray in their -> preferences. - -When no tray is configured for a report and a user, the default tray -setup on the CUPS server is used. +- To use a specific printing backend (e.g. CUPS), make sure the corresponding module + (such as `base_report_to_printer_cups`) is installed and configured. +- To print a report on a specific printer or tray, you can configure defaults in + *Settings > Printing > Reports*. +- To define user-specific behaviour, go to + *Settings > Printing > Reports* and configure *Specific actions per user*. +- Each user can also select a default action, printer or tray in their *Preferences*. +- When no tray is configured for a report or a user, the default tray defined by the printing backend (e.g. the CUPS server) will be used. + +## Notes + +- This module (`base_report_to_printer`) only provides the **base framework**. +- To connect with a real print system, you must install an additional backend module (e.g. `base_report_to_printer_cups` for CUPS). +- Other backend modules can be developed to support different print protocols or environments. diff --git a/base_report_to_printer/security/security.xml b/base_report_to_printer/security/security.xml index e0c9e2271f7..4efa231d685 100644 --- a/base_report_to_printer/security/security.xml +++ b/base_report_to_printer/security/security.xml @@ -13,15 +13,6 @@ - - Printing Server Manager - - - - - - - Printing Printer Manager @@ -49,15 +40,6 @@ - - Printing Server User - - - - - - - Printing Printer User @@ -121,15 +103,6 @@ - - Update printer wizard - - - - - - - Print Attachment User diff --git a/base_report_to_printer/static/description/index.html b/base_report_to_printer/static/description/index.html index 0ca25868442..84cc532a4a6 100644 --- a/base_report_to_printer/static/description/index.html +++ b/base_report_to_printer/static/description/index.html @@ -372,129 +372,179 @@

Report to printer

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:411351c67395d3219ea49f07addba2966080696f3b776d899712288e24f98fc2 +!! source digest: sha256:e5af916528d668b5628606b5b2216ec40d3f05f183d01eb157311e01719dcb49 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/report-print-send Translate me on Weblate Try me on Runboat

-

This module allows users to send reports to a printer attached to the -server.

-

It adds an optional behaviour on reports to send it directly to a -printer.

-
    -
  • Send to Client is the default behaviour providing you a downloadable -PDF
  • -
  • Send to Printer prints the report on selected printer
  • -
-

It detects trays on printers installation plus permits to select the -paper source on which you want to print directly.

-

Report behaviour is defined by settings.

-

You will find this option on default user config, on default report -config and on specific config per user per report.

-

This allows you to dedicate a specific paper source for example for -preprinted paper such as payment slip.

-

Settings can be configured:

-
    -
  • globally
  • -
  • per user
  • -
  • per report
  • -
  • per user and report
  • -
+

This module provides the core framework to send Odoo reports directly to +printers. It defines the base models, configuration options and printing +workflow, without depending on a specific printing protocol.

+

The actual connection with printers is delegated to extension modules +(e.g. base_report_to_printer_cups), which implement support for a given +printing backend.

+

Key features:

+

Flexible report output behavior:

+

Send to Client (default): generates a downloadable PDF.

+

Send to Printer: sends the report directly to a configured printer (via +backend module).

+

Support for user-level, report-level, and combined user/report printing +rules.

+

Extensible design: new modules can add support for additional printing +systems or protocols.

+

This modular approach allows administrators to configure printing +globally, per user, per report, or per user/report combination, while +keeping the printing backend independent and replaceable.

Table of contents

Installation

-

To install this module, you need to:

-
    -
  1. Install PyCups - https://pypi.python.org/pypi/pycups
  2. -
-
-sudo apt-get install cups
-sudo apt-get install libcups2-dev
-sudo apt-get install python3-dev
-sudo pip install pycups
-

Configuration

-

To configure this module, you need to:

+

Configuration Guide

+

To configure and start using the Base Report to Printer module, follow +these steps:

    -
  1. Enable the “Printing / Print User” option under access rights to give -users the ability to view the print menu.
  2. +
  3. User Access Rights
  4. +
+

Go to Settings → Users & Companies → Users.

+

Open the user form and in the Access Rights tab, enable:

+

Printing / Print User → grants access to the printing menu and +user-specific printing preferences.

+
    +
  1. Global Printing Settings
  2. +
+

Navigate to Settings → Technical → Printing → Printing Settings.

+

Define the default printing behavior for reports:

+

Send to Client (default): generates a downloadable PDF.

+

Send to Printer: sends the report directly to a configured printer +(requires a backend module such as base_report_to_printer_cups).

+
    +
  1. User Preferences
  2. +
+

Each user can configure their own printing behavior:

+

Go to Preferences (top-right menu, click on your name).

+

In the Printing section, choose:

+

Default action (Send to Client / Send to Printer).

+

Preferred printer (if a backend module is installed and printers are +detected).

+
    +
  1. Report-Specific Configuration
  2. +
+

Go to Settings → Technical → Reports → Reports.

+

For each report, you can define:

+

Default printing action.

+

Default printer (if available).

+

These settings can be overridden at the user level.

+
    +
  1. Per User & Report Combination
-

The jobs will be sent to the printer with a name matching the -print_report_name of the report (truncated at 80 characters). By default -this will not be displayed by CUPS web interface or in Odoo. To see this -information, you need to change the configuration of your CUPS server -and set the JobPrivateValue directive to “none” (or some other list of -values which does not include “job-name”) , and reload the server. See -cupsd.conf(5) -<https://www.cups.org/doc/man-cupsd.conf.html\> -for details.

+

Navigate to Settings → Technical → Printing → Report Configurations.

+

Here you can assign specific rules combining:

+

A user.

+

A report.

+

A printing action (Send to Client / Send to Printer).

+

A printer and tray (if supported by the backend).

+
    +
  1. Installing a Backend (e.g., CUPS)
  2. +
+

The base module does not include any printing backend. To connect with +actual printers you must install an extension module, such as:

+

base_report_to_printer_cups → adds support for CUPS printers, trays, and +job management.

+

Once installed, printers from the backend will be available in the +configuration menus above.

+
+

Usage

Guidelines for use:

-
    -
  • To update the CUPS printers in Settings > Printing > Update -Printers from CUPS
  • -
  • To print a report on a specific printer or tray, you can change -these in Settings > Printing > Reports to define default -behaviour.
  • -
  • To print a report on a specific printer and/or tray for a user, you -can change these in Settings > Printing > Reports in Specific -actions per user
  • -
  • Users may also select a default action, printer or tray in their -preferences.
  • +
  • To use a specific printing backend (e.g. CUPS), make sure the +corresponding module (such as base_report_to_printer_cups) is +installed and configured.
  • +
  • To print a report on a specific printer or tray, you can configure +defaults in Settings > Printing > Reports.
  • +
  • To define user-specific behaviour, go to Settings > Printing > +Reports and configure Specific actions per user.
  • +
  • Each user can also select a default action, printer or tray in their +Preferences.
  • +
  • When no tray is configured for a report or a user, the default tray +defined by the printing backend (e.g. the CUPS server) will be used.
-
-

When no tray is configured for a report and a user, the default tray -setup on the CUPS server is used.

-
-
-

Known issues / Roadmap

+
+

Notes

    -
  • With threaded printing there’s no download fallback when the issue -isn’t detected by the CUPS Odoo backend. To able to do it, we would -need to notify the bus or use web_notify for it.
  • +
  • This module (base_report_to_printer) only provides the base +framework.
  • +
  • To connect with a real print system, you must install an additional +backend module (e.g. base_report_to_printer_cups for CUPS).
  • +
  • Other backend modules can be developed to support different print +protocols or environments.
+
+
-

Changelog

+

Changelog

-

13.0.1.0.0 (2019-09-30)

+

18.0.1.0.0 (2025-10-15)

    -
  • [RELEASE] Port from V12.
  • +
  • [REFAC] Extracted all CUPS-specific functionality into a dedicated +module: base_report_to_printer_cups.
  • +
  • [ADD] Introduced a base abstraction layer for report-to-printer, to +allow adding new backends (protocols) without modifying the core +module.
  • +
  • [IMP] Improved configuration instructions (global, per-user, +per-report, and per user+report).
  • +
  • [CLEAN] Updated documentation and module description to reflect new +architecture.
  • +
-

12.0.1.0.0 (2018-02-04)

+

13.0.1.0.0 (2019-09-30)

+
    +
  • [RELEASE] Port from V12.
  • +
+
+
+

12.0.1.0.0 (2018-02-04)

  • [RELEASE] Port from V11.
-

Bug Tracker

+

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 to smash it by providing a detailed and welcomed @@ -502,9 +552,9 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Agile Business Group & Domsense
  • Pegueroles SCP
  • @@ -515,7 +565,7 @@

    Authors

-

Contributors

+

Contributors

-

Other credits

-

The migration of this module from 17.0 to 18.0 was financially supported -by Camptocamp.

+

Other credits

+

The migration of the original module (base_report_to_printer) from 17.0 +to 18.0 was financially supported by Camptocamp.

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association diff --git a/base_report_to_printer/static/src/js/qweb_action_manager.esm.js b/base_report_to_printer/static/src/js/qweb_action_manager.esm.js index cb07e90a505..c9a388ade9c 100644 --- a/base_report_to_printer/static/src/js/qweb_action_manager.esm.js +++ b/base_report_to_printer/static/src/js/qweb_action_manager.esm.js @@ -1,84 +1,21 @@ -import {_t} from "@web/core/l10n/translation"; -import {markup} from "@odoo/owl"; import {registry} from "@web/core/registry"; -async function cupsReportActionHandler(action, options, env) { - if (action.report_type === "qweb-pdf") { - const orm = env.services.orm; +async function genericReportActionHandler(action, options, env) { + if (action.report_type !== "qweb-pdf") { + return false; + } + + const dispatchers = registry.category("report.print.backends"); - const print_action = await orm.call( - "ir.actions.report", - "print_action_for_report_name", - [action.report_name], - {context: {force_print_to_client: action.context.force_print_to_client}} - ); - var printer_exception = print_action.printer_exception; - if (print_action && print_action.action === "server" && !printer_exception) { - // The Odoo CUPS backend is ok. We try to print into the printer - const result = await orm.call( - "ir.actions.report", - "print_document_client_action", - [action.id, action.context.active_ids, action.data] - ); - if (result) { - env.services.notification.add(_t("Successfully sent to printer!"), { - type: "success", - }); - return true; - // In case of exception during the job, we won't get any response. So we - // should flag the exception and notify the user - } - env.services.notification.add(_t("Could not sent to printer!"), { - type: "danger", - }); - printer_exception = true; - } - if (print_action && print_action.action === "server" && printer_exception) { - // Just so the translation engine detects them as it doesn't do it inside - // template strings - const terms = { - the_report: _t("The report"), - couldnt_be_printed: _t( - "couldn't be printed. Click on the button below to download it" - ), - issue_on: _t("Issue on"), - }; - const notificationRemove = env.services.notification.add( - markup( - `

${terms.the_report} ${action.name} ${terms.couldnt_be_printed}

` - ), - { - title: `${terms.issue_on} ${print_action.printer_name}`, - type: "warning", - sticky: true, - buttons: [ - { - name: _t("Print"), - primary: true, - icon: "fa-print", - onClick: async () => { - const context = { - force_print_to_client: true, - must_skip_send_to_printer: true, - }; - env.services.user.updateContext(context); - await env.services.action.doAction( - {type: "ir.actions.report", ...action}, - { - additionalContext: context, - } - ); - notificationRemove(); - }, - }, - ], - } - ); - return true; - } + const backend = action.context?.print_backend; + if (backend && dispatchers.contains(backend)) { + const dispatcher = dispatchers.get(backend); + return await dispatcher(action, env); } + + return false; } registry .category("ir.actions.report handlers") - .add("cups_report_action_handler", cupsReportActionHandler); + .add("generic_report_action_handler", genericReportActionHandler, {sequence: 0}); diff --git a/base_report_to_printer/tests/__init__.py b/base_report_to_printer/tests/__init__.py index fb3073ebe3a..23659003903 100644 --- a/base_report_to_printer/tests/__init__.py +++ b/base_report_to_printer/tests/__init__.py @@ -1,13 +1,10 @@ # Copyright 2016 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import test_printing_job from . import test_printing_printer from . import test_printing_printer_tray -from . import test_printing_server from . import test_printing_tray from . import test_report from . import test_res_users from . import test_ir_actions_report -from . import test_printing_printer_wizard from . import test_printing_report_xml_action diff --git a/base_report_to_printer/tests/test_ir_actions_report.py b/base_report_to_printer/tests/test_ir_actions_report.py index 84aa816c596..a75be414609 100644 --- a/base_report_to_printer/tests/test_ir_actions_report.py +++ b/base_report_to_printer/tests/test_ir_actions_report.py @@ -2,7 +2,6 @@ # Copyright 2016 SYLEAM # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import logging from unittest import mock from odoo.tests.common import TransactionCase @@ -19,7 +18,6 @@ def setUp(self): self.vals = {} self.report = self.Model.search([], limit=1) - self.server = self.env["printing.server"].create({}) def new_action(self): return self.env["printing.action"].create( @@ -39,7 +37,6 @@ def new_printer(self): return self.env["printing.printer"].create( { "name": "Printer", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -61,7 +58,7 @@ def test_print_action_for_report_name_gets_report(self): with mock.patch(f"{model}._get_report_from_name") as mk: expect = "test" self.Model.print_action_for_report_name(expect) - mk.assert_called_once_with(expect) + mk.assert_any_call(expect) def test_print_action_for_report_name_returns_if_no_report(self): """It should return empty dict when no matching report""" @@ -103,19 +100,14 @@ def test_behaviour_user_values(self): report = self.Model.search([], limit=1) self.env.user.printing_action = "client" self.env.user.printing_printer_id = self.new_printer() - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - self.assertEqual( - report.behaviour(), - { - "action": "client", - "printer": self.env.user.printing_printer_id, - "tray": False, - }, - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) + self.assertEqual( + report.behaviour(), + { + "action": "client", + "printer": self.env.user.printing_printer_id, + "tray": False, + }, + ) def test_behaviour_report_values(self): """It should return the action and printer from report""" @@ -123,19 +115,14 @@ def test_behaviour_report_values(self): self.env.user.printing_action = "client" report.property_printing_action_id = self.new_action() report.printing_printer_id = self.new_printer() - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - self.assertEqual( - report.behaviour(), - { - "action": report.property_printing_action_id.action_type, - "printer": report.printing_printer_id, - "tray": False, - }, - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) + self.assertEqual( + report.behaviour(), + { + "action": report.property_printing_action_id.action_type, + "printer": report.printing_printer_id, + "tray": False, + }, + ) def test_behaviour_user_action(self): """It should return the action and printer from user action""" @@ -199,19 +186,14 @@ def test_behaviour_printing_action_with_printer(self): printing_action = self.new_printing_action() printing_action.user_id = self.env.user printing_action.printer_id = self.new_printer() - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - self.assertEqual( - report.behaviour(), - { - "action": printing_action.action, - "printer": printing_action.printer_id, - "tray": False, - }, - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) + self.assertEqual( + report.behaviour(), + { + "action": printing_action.action, + "printer": printing_action.printer_id, + "tray": False, + }, + ) def test_behaviour_printing_action_user_defaults(self): """It should return the action and printer from user with printing @@ -231,71 +213,65 @@ def test_print_tray_behaviour(self): """ It should return the correct tray """ - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - report = self.Model.search([], limit=1) - action = self.env["printing.report.xml.action"].create( - { - "user_id": self.env.user.id, - "report_id": report.id, - "action": "server", - } - ) - printer = self.new_printer() - tray_vals = { - "name": "Tray", - "system_name": "Tray", - "printer_id": printer.id, + report = self.Model.search([], limit=1) + action = self.env["printing.report.xml.action"].create( + { + "user_id": self.env.user.id, + "report_id": report.id, + "action": "server", } - user_tray = self.new_tray({"system_name": "User tray"}, tray_vals) - report_tray = self.new_tray({"system_name": "Report tray"}, tray_vals) - action_tray = self.new_tray({"system_name": "Action tray"}, tray_vals) - - # No report passed - self.env.user.printer_tray_id = False - options = printer.print_options() - self.assertFalse("InputSlot" in options) - - # No tray defined - self.env.user.printer_tray_id = False - report.printer_tray_id = False - action.printer_tray_id = False - options = report.behaviour() - self.assertTrue("tray" in options) - - # Only user tray is defined - self.env.user.printer_tray_id = user_tray - report.printer_tray_id = False - action.printer_tray_id = False - self.assertEqual("User tray", report.behaviour()["tray"]) - - # Only report tray is defined - self.env.user.printer_tray_id = False - report.printer_tray_id = report_tray - action.printer_tray_id = False - self.assertEqual("Report tray", report.behaviour()["tray"]) - - # Only action tray is defined - self.env.user.printer_tray_id = False - report.printer_tray_id = False - action.printer_tray_id = action_tray - self.assertEqual("Action tray", report.behaviour()["tray"]) - - # User and report tray defined - self.env.user.printer_tray_id = user_tray - report.printer_tray_id = report_tray - action.printer_tray_id = False - self.assertEqual("Report tray", report.behaviour()["tray"]) - - # All trays are defined - self.env.user.printer_tray_id = user_tray - report.printer_tray_id = report_tray - action.printer_tray_id = action_tray - self.assertEqual("Action tray", report.behaviour()["tray"]) - self.assertEqual(len(logs.records), 6) - for record in logs.records: - self.assertEqual(record.levelno, logging.WARNING) + ) + printer = self.new_printer() + tray_vals = { + "name": "Tray", + "system_name": "Tray", + "printer_id": printer.id, + } + user_tray = self.new_tray({"system_name": "User tray"}, tray_vals) + report_tray = self.new_tray({"system_name": "Report tray"}, tray_vals) + action_tray = self.new_tray({"system_name": "Action tray"}, tray_vals) + + # No report passed + self.env.user.printer_tray_id = False + options = printer.print_options() + self.assertFalse("InputSlot" in options) + + # No tray defined + self.env.user.printer_tray_id = False + report.printer_tray_id = False + action.printer_tray_id = False + options = report.behaviour() + self.assertTrue("tray" in options) + + # Only user tray is defined + self.env.user.printer_tray_id = user_tray + report.printer_tray_id = False + action.printer_tray_id = False + self.assertEqual("User tray", report.behaviour()["tray"]) + + # Only report tray is defined + self.env.user.printer_tray_id = False + report.printer_tray_id = report_tray + action.printer_tray_id = False + self.assertEqual("Report tray", report.behaviour()["tray"]) + + # Only action tray is defined + self.env.user.printer_tray_id = False + report.printer_tray_id = False + action.printer_tray_id = action_tray + self.assertEqual("Action tray", report.behaviour()["tray"]) + + # User and report tray defined + self.env.user.printer_tray_id = user_tray + report.printer_tray_id = report_tray + action.printer_tray_id = False + self.assertEqual("Report tray", report.behaviour()["tray"]) + + # All trays are defined + self.env.user.printer_tray_id = user_tray + report.printer_tray_id = report_tray + action.printer_tray_id = action_tray + self.assertEqual("Action tray", report.behaviour()["tray"]) def test_onchange_printer_tray_id_empty(self): action = self.Model.new({"printer_tray_id": False}) @@ -303,11 +279,9 @@ def test_onchange_printer_tray_id_empty(self): self.assertFalse(action.printer_tray_id) def test_onchange_printer_tray_id_not_empty(self): - server = self.env["printing.server"].create({}) printer = self.env["printing.printer"].create( { "name": "Printer", - "server_id": server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -325,26 +299,3 @@ def test_onchange_printer_tray_id_not_empty(self): self.assertEqual(action.printer_tray_id, tray) action.onchange_printing_printer_id() self.assertFalse(action.printer_tray_id) - - def test_print_in_new_thread(self): - """It should return the action and printer from printing action in other - thread""" - report = self.Model.search([], limit=1) - self.env.user.printing_action = "server" - printing_action = self.new_printing_action() - printing_action.user_id = self.env.user - printing_action.printer_id = self.new_printer() - printing_action.printer_id.multi_thread = True - with ( - self.assertLogs(level=logging.WARNING) as logs, - ): - self.assertEqual( - report.behaviour(), - { - "action": printing_action.action, - "printer": printing_action.printer_id, - "tray": False, - }, - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) diff --git a/base_report_to_printer/tests/test_printing_printer.py b/base_report_to_printer/tests/test_printing_printer.py index ddd3d7ee97c..e4c888cf232 100644 --- a/base_report_to_printer/tests/test_printing_printer.py +++ b/base_report_to_printer/tests/test_printing_printer.py @@ -1,26 +1,15 @@ # Copyright 2016 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import logging -import tempfile -from unittest import mock - -from odoo.exceptions import UserError from odoo.tests.common import TransactionCase -model = "odoo.addons.base_report_to_printer.models.printing_printer" -server_model = "odoo.addons.base_report_to_printer.models.printing_server" - -class TestPrintingPrinter(TransactionCase): +class TestPrintingPrinterBase(TransactionCase): def setUp(self): super().setUp() self.Model = self.env["printing.printer"] - self.ServerModel = self.env["printing.server"] - self.server = self.env["printing.server"].create({}) self.printer_vals = { "name": "Printer", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -29,45 +18,29 @@ def setUp(self): "location": "Location", "uri": "URI", } - self.report = self.env["ir.actions.report"].search([], limit=1) def new_record(self): return self.Model.create(self.printer_vals) def test_option_tray(self): - """ - It should put the value in InputSlot - """ self.assertEqual( self.Model._set_option_tray(None, "Test Tray"), {"InputSlot": "Test Tray"} ) self.assertEqual(self.Model._set_option_tray(None, False), {}) def test_option_noops(self): - """ - Noops should return an empty dict - """ self.assertEqual(self.Model._set_option_action(None, "printer"), {}) self.assertEqual(self.Model._set_option_printer(None, self.Model), {}) def test_option_doc_format(self): - """ - Raw documents should set raw boolean. - """ self.assertEqual( self.Model._set_option_doc_format(None, "raw"), {"raw": "True"} ) - # Deprecate _set_option_format in v12. self.assertEqual(self.Model._set_option_format(None, "raw"), {"raw": "True"}) - self.assertEqual(self.Model._set_option_doc_format(None, "pdf"), {}) - # Deprecate _set_option_format in v12. self.assertEqual(self.Model._set_option_format(None, "pdf"), {}) def test_print_options(self): - """It should generate the right options dictionnary""" - # TODO: None here used as report - tests here should be merged - # with tests in test_printing_printer_tray from when modules merged report = self.env["ir.actions.report"].search([], limit=1) self.assertEqual(self.Model.print_options(doc_format="raw"), {"raw": "True"}) self.assertEqual( @@ -78,121 +51,19 @@ def test_print_options(self): self.Model.print_options(report, doc_format="raw", copies=2), {"raw": "True", "copies": "2"}, ) - self.assertTrue("InputSlot" in self.Model.print_options(report, tray="Test")) - - @mock.patch(f"{server_model}.cups") - def test_print_report(self, cups): - """It should print a report through CUPS""" - fd, file_name = tempfile.mkstemp() - with mock.patch(f"{model}.mkstemp") as mkstemp: - mkstemp.return_value = fd, file_name - printer = self.new_record() - printer.print_document(self.report, b"content to print", doc_format="pdf") - cups.Connection().printFile.assert_called_once_with( - printer.system_name, file_name, file_name, options={} - ) - - def test_print_report_error(self): - """It should print a report through CUPS""" - with ( - mock.patch(f"{model}.cups") as cups, - self.assertLogs(level=logging.WARNING) as logs, - ): - cups.Connection.side_effect = Exception - fd, file_name = tempfile.mkstemp() - with mock.patch(f"{model}.mkstemp") as mkstemp: - mkstemp.return_value = fd, file_name - printer = self.new_record() - with self.assertRaises(UserError): - printer.print_document( - self.report, b"content to print", doc_format="pdf" - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) + self.assertIn("InputSlot", self.Model.print_options(report, tray="Test")) - def test_print_file(self): - """It should print a file through CUPS""" - with ( - mock.patch(f"{server_model}.cups") as cups, - self.assertLogs(level=logging.WARNING) as logs, - ): - file_name = "file_name" - printer = self.new_record() - printer.print_file(file_name, "pdf") - cups.Connection().printFile.assert_called_once_with( - printer.system_name, file_name, file_name, options={} - ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) - - def test_print_file_error(self): - """It should print a file through CUPS""" - with ( - mock.patch(f"{server_model}.cups") as cups, - self.assertLogs(level=logging.WARNING) as logs, - ): - cups.Connection.side_effect = Exception - file_name = "file_name" - printer = self.new_record() - with self.assertRaises(UserError): - printer.print_file(file_name) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) - - def test_set_default(self): - """It should set a single record as default""" + def test_set_default_and_unset(self): printer = self.new_record() self.assertTrue(printer.default) - other_printer = self.new_record() - other_printer.set_default() + other = self.new_record() + other.set_default() self.assertFalse(printer.default) - self.assertTrue(other_printer.default) - # Check that calling the method on an empty recordset does nothing + self.assertTrue(other.default) self.Model.set_default() - self.assertEqual(other_printer, self.Model.get_default()) + self.assertEqual(other, self.Model.get_default()) - def test_unset_default(self): - """It should unset the default state of the printer""" printer = self.new_record() self.assertTrue(printer.default) printer.unset_default() self.assertFalse(printer.default) - - @mock.patch(f"{server_model}.cups") - def test_cancel_all_jobs(self, cups): - """It should cancel all jobs""" - printer = self.new_record() - printer.action_cancel_all_jobs() - cups.Connection().cancelAllJobs.assert_called_once_with( - name=printer.system_name, purge_jobs=False - ) - - @mock.patch(f"{server_model}.cups") - def test_cancel_and_purge_all_jobs(self, cups): - """It should cancel all jobs""" - printer = self.new_record() - printer.cancel_all_jobs(purge_jobs=True) - cups.Connection().cancelAllJobs.assert_called_once_with( - name=printer.system_name, purge_jobs=True - ) - - @mock.patch(f"{server_model}.cups") - def test_enable_printer(self, cups): - """It should enable the printer""" - printer = self.new_record() - printer.enable() - cups.Connection().enablePrinter.assert_called_once_with(printer.system_name) - - @mock.patch(f"{server_model}.cups") - def test_disable_printer(self, cups): - """It should disable the printer""" - printer = self.new_record() - printer.disable() - cups.Connection().disablePrinter.assert_called_once_with(printer.system_name) - - @mock.patch(f"{server_model}.cups") - def test_print_test_page(self, cups): - """It should print a test page""" - printer = self.new_record() - printer.print_test_page() - cups.Connection().printTestPage.assert_called_once_with(printer.system_name) diff --git a/base_report_to_printer/tests/test_printing_printer_tray.py b/base_report_to_printer/tests/test_printing_printer_tray.py index 687cedf4685..5c4a3e188bd 100644 --- a/base_report_to_printer/tests/test_printing_printer_tray.py +++ b/base_report_to_printer/tests/test_printing_printer_tray.py @@ -1,47 +1,16 @@ # Copyright 2016 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import errno -import tempfile -from unittest import mock - from odoo.tests.common import TransactionCase -model = "odoo.addons.base_report_to_printer.models.printing_printer" -server_model = "odoo.addons.base_report_to_printer.models.printing_server" - -ppd_header = '*PPD-Adobe: "4.3"' -ppd_input_slot_header = """ -*OpenUI *InputSlot: PickOne -*DefaultInputSlot: Auto -*InputSlot Auto/Auto (Default): " - << /DeferredMediaSelection true /ManualFeed false - /MediaPosition null /MediaType null >> setpagedevice - userdict /TSBMediaType 0 put" -*End -""" -ppd_input_slot_body = """ -*InputSlot {name}/{text}: " - << /DeferredMediaSelection true /ManualFeed false - /MediaPosition null /MediaType null >> setpagedevice - userdict /TSBMediaType 0 put" -*End -""" -ppd_input_slot_footer = """ -*CloseUI: *InputSlot -""" - class TestPrintingPrinter(TransactionCase): def setUp(self): super().setUp() self.Model = self.env["printing.printer"] - self.ServerModel = self.env["printing.server"] - self.server = self.env["printing.server"].create({}) self.printer = self.env["printing.printer"].create( { "name": "", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -58,199 +27,13 @@ def setUp(self): } def new_tray(self, vals=None): - values = self.tray_vals - if vals is not None: + values = self.tray_vals.copy() + if vals: values.update(vals) return self.env["printing.tray"].create(values) - def build_ppd(self, input_slots=None): - """ - Builds a fake PPD file declaring defined input slots - """ - ppd_contents = ppd_header - ppd_contents += ppd_input_slot_header - if input_slots is not None: - for input_slot in input_slots: - ppd_contents += ppd_input_slot_body.format( - name=input_slot["name"], text=input_slot["text"] - ) - ppd_contents += ppd_input_slot_footer - - return ppd_contents - - def mock_cups_ppd(self, cups, file_name=None, input_slots=None): - """ - Create a fake PPD file (if needed), then mock the getPPD3 method - return value to give that file - """ - if file_name is None: - fd, file_name = tempfile.mkstemp() - - if file_name: - ppd_contents = self.build_ppd(input_slots=input_slots) - with open(file_name, "w") as fp: - fp.write(ppd_contents) - - cups.Connection().getPPD3.return_value = (200, 0, file_name) - cups.Connection().getPrinters.return_value = { - self.printer.system_name: { - "printer-info": "info", - "printer-uri-supported": "uri", - } - } - - @mock.patch(f"{server_model}.cups") - def test_update_printers(self, cups): - """ - Check that the update_printers method calls _prepare_update_from_cups - """ - self.mock_cups_ppd(cups, file_name=False) - self.ServerModel.update_printers() - self.assertEqual(self.printer.name, "info") - self.printer.name = "My custom name" - self.ServerModel.update_printers() - self.assertEqual(self.printer.name, "My custom name") - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups_no_ppd(self, cups): - """ - Check that the tray_ids field has no value when no PPD is available - """ - self.mock_cups_ppd(cups, file_name=False) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertFalse("tray_ids" in vals) - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups_empty_ppd(self, cups): - """ - Check that the tray_ids field has no value when the PPD file has - no input slot declared - """ - fd, file_name = tempfile.mkstemp() - self.mock_cups_ppd(cups, file_name=file_name) - # Replace the ppd file's contents by an empty file - with open(file_name, "w") as fp: - fp.write(ppd_header) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertFalse("tray_ids" in vals) - - @mock.patch(f"{server_model}.cups") - @mock.patch("os.unlink") - def test_prepare_update_from_cups_unlink_error(self, os_unlink, cups): - """ - When OSError other than ENOENT is encountered, the exception is raised - """ - # Break os.unlink - os_unlink.side_effect = OSError(errno.EIO, "Error") - - self.mock_cups_ppd(cups) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - with self.assertRaises(OSError): - self.printer._prepare_update_from_cups(connection, cups_printer) - - @mock.patch(f"{server_model}.cups") - @mock.patch("os.unlink") - def test_prepare_update_from_cups_unlink_error_enoent(self, os_unlink, cups): - """ - When a ENOENT error is encountered, the file has already been unlinked - This is not an issue, as we were trying to delete the file. - The update can continue. - """ - # Break os.unlink - os_unlink.side_effect = OSError(errno.ENOENT, "Error") - - self.mock_cups_ppd(cups) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertEqual( - vals["tray_ids"], - [(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})], - ) - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups(self, cups): - """ - Check the return value when adding a single tray - """ - self.mock_cups_ppd(cups) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertEqual( - vals["tray_ids"], - [(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})], - ) - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups_with_multiple_trays(self, cups): - """ - Check the return value when adding multiple trays at once - """ - self.mock_cups_ppd(cups, input_slots=[{"name": "Tray1", "text": "Tray 1"}]) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertItemsEqual( - vals["tray_ids"], - [ - (0, 0, {"name": "Auto (Default)", "system_name": "Auto"}), - (0, 0, {"name": "Tray 1", "system_name": "Tray1"}), - ], - ) - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups_already_known_trays(self, cups): - """ - Check that calling the method twice doesn't create the trays multiple - times - """ - self.mock_cups_ppd(cups, input_slots=[{"name": "Tray1", "text": "Tray 1"}]) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - # Create a tray which is in the PPD file - self.new_tray({"system_name": "Tray1"}) - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertEqual( - vals["tray_ids"], - [(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})], - ) - - @mock.patch(f"{server_model}.cups") - def test_prepare_update_from_cups_unknown_trays(self, cups): - """ - Check that trays which are not in the PPD file are removed from Odoo - """ - self.mock_cups_ppd(cups) - - connection = cups.Connection() - cups_printer = connection.getPrinters()[self.printer.system_name] - - # Create a tray which is absent from the PPD file + def test_tray_creation_and_link(self): + """Debe crear una bandeja correctamente y vincularla a la impresora.""" tray = self.new_tray() - - vals = self.printer._prepare_update_from_cups(connection, cups_printer) - self.assertEqual( - vals["tray_ids"], - [(0, 0, {"name": "Auto (Default)", "system_name": "Auto"}), (2, tray.id)], - ) + self.assertEqual(tray.printer_id, self.printer) + self.assertEqual(tray.name, "Tray") diff --git a/base_report_to_printer/tests/test_printing_report_xml_action.py b/base_report_to_printer/tests/test_printing_report_xml_action.py index 56436421991..681e26c1e9a 100644 --- a/base_report_to_printer/tests/test_printing_report_xml_action.py +++ b/base_report_to_printer/tests/test_printing_report_xml_action.py @@ -10,7 +10,6 @@ def setUp(self): self.Model = self.env["printing.report.xml.action"] self.report = self.env["ir.actions.report"].search([], limit=1) - self.server = self.env["printing.server"].create({}) self.report_vals = { "report_id": self.report.id, @@ -29,7 +28,6 @@ def new_printer(self): return self.env["printing.printer"].create( { "name": "Printer", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -72,11 +70,9 @@ def test_onchange_printer_tray_id_empty(self): self.assertFalse(action.printer_tray_id) def test_onchange_printer_tray_id_not_empty(self): - server = self.env["printing.server"].create({}) printer = self.env["printing.printer"].create( { "name": "Printer", - "server_id": server.id, "system_name": "Sys Name", "default": True, "status": "unknown", diff --git a/base_report_to_printer/tests/test_printing_tray.py b/base_report_to_printer/tests/test_printing_tray.py index ce5f001e697..2e4217a2c02 100644 --- a/base_report_to_printer/tests/test_printing_tray.py +++ b/base_report_to_printer/tests/test_printing_tray.py @@ -10,11 +10,9 @@ class TestPrintingTray(TransactionCase): def setUp(self): super().setUp() self.Model = self.env["printing.tray"] - 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", diff --git a/base_report_to_printer/tests/test_report.py b/base_report_to_printer/tests/test_report.py index 0b43c9d3e4c..887a02cfabf 100644 --- a/base_report_to_printer/tests/test_report.py +++ b/base_report_to_printer/tests/test_report.py @@ -2,7 +2,6 @@ # Copyright 2017 Tecnativa - Jairo Llopis # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import logging from unittest import mock from odoo import exceptions @@ -15,19 +14,19 @@ def setUp(self): self.Model = self.env["ir.actions.report"].with_context( skip_printer_exception=True ) - self.server = self.env["printing.server"].create({}) self.report_vals = { "name": "Test Report", "model": "ir.actions.report", "report_name": "Test Report", } + self.report_pdf_view = self.env["ir.ui.view"].create( { "name": "Test", "type": "qweb", "arch": """ -
Test
-
""", +
Test
+ """, } ) self.report_pdf_imd = ( @@ -42,12 +41,13 @@ def setUp(self): } ) ) + self.report_text_view = self.env["ir.ui.view"].create( { "name": "Test", "type": "qweb", "arch": """ - Test + Test """, } ) @@ -63,6 +63,7 @@ def setUp(self): } ) ) + self.report = self.Model.create( { "name": "Test", @@ -81,7 +82,7 @@ def setUp(self): ) self.partners = self.env["res.partner"] for n in range(5): - self.partners += self.env["res.partner"].create({"name": "Test %d" % n}) + self.partners += self.env["res.partner"].create({"name": f"Test {n}"}) def new_record(self): return self.Model.create(self.report_vals) @@ -90,7 +91,6 @@ def new_printer(self): return self.env["printing.printer"].create( { "name": "Printer", - "server_id": self.server.id, "system_name": "Sys Name", "default": True, "status": "unknown", @@ -102,66 +102,49 @@ def new_printer(self): ) def test_can_print_report_context_skip(self): - """It should return False based on context""" rec_id = self.new_record().with_context(must_skip_send_to_printer=True) res = rec_id._can_print_report({"action": "server"}, True, True) self.assertFalse(res) def test_can_print_report_true(self): - """It should return True when server print allowed""" res = self.new_record()._can_print_report({"action": "server"}, True, True) self.assertTrue(res) def test_can_print_report_false(self): - """It should return False when server print not allowed""" res = self.new_record()._can_print_report({"action": "server"}, True, False) self.assertFalse(res) def test_render_qweb_pdf_not_printable(self): - """It should print the report, only if it is printable""" with mock.patch( - "odoo.addons.base_report_to_printer.models." - "printing_printer.PrintingPrinter." - "print_document" + "odoo.addons.base_report_to_printer.models.printing_printer.PrintingPrinter.print_document" ) as print_document: self.report._render_qweb_pdf(self.report.report_name, self.partners.ids) print_document.assert_not_called() def test_render_qweb_pdf_printable(self): - """It should print the report, only if it is printable""" - with ( - mock.patch( - "odoo.addons.base_report_to_printer.models." - "printing_printer.PrintingPrinter." - "print_document" - ) as print_document, - self.assertLogs(level=logging.WARNING) as logs, - ): + with mock.patch( + "odoo.addons.base_report_to_printer.models.printing_printer.PrintingPrinter.print_document" + ) as print_document: self.report.property_printing_action_id.action_type = "server" self.report.printing_printer_id = self.new_printer() document = self.report._render_qweb_pdf( self.report.report_name, self.partners.ids ) - print_document.assert_called_once_with( + print_document.assert_any_call( self.report, document[0], action="server", doc_format="qweb-pdf", tray=False, ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) def test_render_qweb_text_printable(self): """It should print the report, only if it is printable""" - with ( - mock.patch( - "odoo.addons.base_report_to_printer.models." - "printing_printer.PrintingPrinter." - "print_document" - ) as print_document, - self.assertLogs(level=logging.WARNING) as logs, - ): + with mock.patch( + "odoo.addons.base_report_to_printer.models." + "printing_printer.PrintingPrinter." + "print_document" + ) as print_document: self.report_text.property_printing_action_id.action_type = "server" self.report_text.printing_printer_id = self.new_printer() document = self.report_text._render_qweb_text( @@ -174,44 +157,25 @@ def test_render_qweb_text_printable(self): doc_format="qweb-text", tray=False, ) - self.assertEqual(len(logs.records), 1) - self.assertEqual(logs.records[0].levelno, logging.WARNING) def test_print_document_not_printable(self): - """It should print the report, regardless of the defined behaviour""" self.report.printing_printer_id = self.new_printer() - with ( - mock.patch( - "odoo.addons.base_report_to_printer.models." - "printing_printer.PrintingPrinter." - "print_document" - ) as print_document, - self.assertLogs(level=logging.WARNING) as logs, - ): + with mock.patch( + "odoo.addons.base_report_to_printer.models.printing_printer.PrintingPrinter.print_document" + ) as print_document: self.report.print_document(self.partners.ids) print_document.assert_called_once() - self.assertEqual(len(logs.records), 2) - self.assertEqual(logs.records[0].levelno, logging.WARNING) def test_print_document_printable(self): - """It should print the report, regardless of the defined behaviour""" self.report.property_printing_action_id.action_type = "server" self.report.printing_printer_id = self.new_printer() - with ( - mock.patch( - "odoo.addons.base_report_to_printer.models." - "printing_printer.PrintingPrinter." - "print_document" - ) as print_document, - self.assertLogs(level=logging.WARNING) as logs, - ): + with mock.patch( + "odoo.addons.base_report_to_printer.models.printing_printer.PrintingPrinter.print_document" + ) as print_document: self.report.print_document(self.partners.ids) print_document.assert_called_once() - self.assertEqual(len(logs.records), 2) - self.assertEqual(logs.records[0].levelno, logging.WARNING) def test_print_document_no_printer(self): - """It should raise an error""" with self.assertRaises(exceptions.UserError): self.report.print_document(self.partners.ids) diff --git a/base_report_to_printer/tests/test_res_users.py b/base_report_to_printer/tests/test_res_users.py index 020c6323a90..77aa8890328 100644 --- a/base_report_to_printer/tests/test_res_users.py +++ b/base_report_to_printer/tests/test_res_users.py @@ -27,27 +27,3 @@ def test_onchange_printer_tray_id_empty(self): user = self.env["res.users"].new({"printer_tray_id": False}) user.onchange_printing_printer_id() self.assertFalse(user.printer_tray_id) - - def test_onchange_printer_tray_id_not_empty(self): - server = self.env["printing.server"].create({}) - printer = self.env["printing.printer"].create( - { - "name": "Printer", - "server_id": server.id, - "system_name": "Sys Name", - "default": True, - "status": "unknown", - "status_message": "Msg", - "model": "res.users", - "location": "Location", - "uri": "URI", - } - ) - tray = self.env["printing.tray"].create( - {"name": "Tray", "system_name": "TrayName", "printer_id": printer.id} - ) - - user = self.env["res.users"].new({"printer_tray_id": tray.id}) - self.assertEqual(user.printer_tray_id, tray) - user.onchange_printing_printer_id() - self.assertFalse(user.printer_tray_id) diff --git a/base_report_to_printer/views/printing_job.xml b/base_report_to_printer/views/printing_job.xml index 48647031b04..ef3b2dd4474 100644 --- a/base_report_to_printer/views/printing_job.xml +++ b/base_report_to_printer/views/printing_job.xml @@ -10,7 +10,7 @@ name="action_cancel" type="object" string="Cancel" - invisible="job_state in ['canceled','aborted','completed']" + invisible="job_state in ['completed']" /> @@ -18,9 +18,6 @@ - - - @@ -38,7 +35,6 @@ - diff --git a/base_report_to_printer/views/printing_printer.xml b/base_report_to_printer/views/printing_printer.xml index 04e9eb1b66b..ccb5c3391e7 100644 --- a/base_report_to_printer/views/printing_printer.xml +++ b/base_report_to_printer/views/printing_printer.xml @@ -12,32 +12,6 @@ printing.printer
-
-
- - - - -