mirror of
https://github.com/OCA/report-print-send.git
synced 2025-02-16 07:11:31 +02:00
271 lines
8.8 KiB
Python
271 lines
8.8 KiB
Python
# Copyright (c) 2007 Ferran Pegueroles <ferran@pegueroles.com>
|
|
# Copyright (c) 2009 Albert Cervera i Areny <albert@nan-tic.com>
|
|
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
|
|
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
|
|
# Copyright (C) 2013-2014 Camptocamp (<http://www.camptocamp.com>)
|
|
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
|
# Copyright (C) 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
|
|
# 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
|
|
|
|
_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"
|
|
|
|
name = fields.Char(required=True, index=True)
|
|
active = fields.Boolean(default=True)
|
|
server_id = fields.Many2one(
|
|
comodel_name="printing.server",
|
|
string="Server",
|
|
required=True,
|
|
help="Server used to access this printer.",
|
|
)
|
|
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)
|
|
default = fields.Boolean(readonly=True)
|
|
status = fields.Selection(
|
|
selection=[
|
|
("unavailable", "Unavailable"),
|
|
("printing", "Printing"),
|
|
("unknown", "Unknown"),
|
|
("available", "Available"),
|
|
("error", "Error"),
|
|
("server-error", "Server Error"),
|
|
],
|
|
required=True,
|
|
readonly=True,
|
|
default="unknown",
|
|
)
|
|
status_message = fields.Char(readonly=True)
|
|
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
|
|
|
|
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
|
|
|
|
def print_options(self, report=None, **print_opts):
|
|
options = {}
|
|
for option, value in print_opts.items():
|
|
try:
|
|
options.update(getattr(self, "_set_option_%s" % option)(report, value))
|
|
except AttributeError:
|
|
options[option] = str(value)
|
|
return options
|
|
|
|
def print_file(self, file_name, report=None, **print_opts):
|
|
"""Print a file"""
|
|
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)
|
|
|
|
_logger.debug(
|
|
"Sending job to CUPS printer %s on %s with options %s"
|
|
% (self.system_name, self.server_id.address, options)
|
|
)
|
|
connection.printFile(self.system_name, file_name, title, options=options)
|
|
_logger.info(
|
|
"Printing job: '{}' on {}".format(file_name, self.server_id.address)
|
|
)
|
|
try:
|
|
os.remove(file_name)
|
|
except OSError as exc:
|
|
_logger.warning("Unable to remove temporary file %s: %s", file_name, exc)
|
|
return True
|
|
|
|
def set_default(self):
|
|
if not self:
|
|
return
|
|
self.ensure_one()
|
|
default_printers = self.search([("default", "=", True)])
|
|
default_printers.unset_default()
|
|
self.write({"default": True})
|
|
return True
|
|
|
|
def unset_default(self):
|
|
self.write({"default": False})
|
|
return True
|
|
|
|
def get_default(self):
|
|
return self.search([("default", "=", True)], limit=1)
|
|
|
|
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")
|