odoo-addon-base-report-to-printer 18.0.1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/base_report_to_printer/README.rst +205 -0
- odoo/addons/base_report_to_printer/__init__.py +9 -0
- odoo/addons/base_report_to_printer/__manifest__.py +39 -0
- odoo/addons/base_report_to_printer/data/neutralize.sql +2 -0
- odoo/addons/base_report_to_printer/data/printing_data.xml +27 -0
- odoo/addons/base_report_to_printer/i18n/am.po +936 -0
- odoo/addons/base_report_to_printer/i18n/base_report_to_printer.pot +932 -0
- odoo/addons/base_report_to_printer/i18n/bg.po +939 -0
- odoo/addons/base_report_to_printer/i18n/ca.po +936 -0
- odoo/addons/base_report_to_printer/i18n/de.po +994 -0
- odoo/addons/base_report_to_printer/i18n/el_GR.po +937 -0
- odoo/addons/base_report_to_printer/i18n/es.po +1004 -0
- odoo/addons/base_report_to_printer/i18n/es_AR.po +987 -0
- odoo/addons/base_report_to_printer/i18n/es_ES.po +937 -0
- odoo/addons/base_report_to_printer/i18n/fi.po +939 -0
- odoo/addons/base_report_to_printer/i18n/fr.po +999 -0
- odoo/addons/base_report_to_printer/i18n/gl.po +936 -0
- odoo/addons/base_report_to_printer/i18n/hr.po +966 -0
- odoo/addons/base_report_to_printer/i18n/hr_HR.po +941 -0
- odoo/addons/base_report_to_printer/i18n/it.po +985 -0
- odoo/addons/base_report_to_printer/i18n/nl.po +939 -0
- odoo/addons/base_report_to_printer/i18n/nl_NL.po +950 -0
- odoo/addons/base_report_to_printer/i18n/pt.po +936 -0
- odoo/addons/base_report_to_printer/i18n/pt_BR.po +940 -0
- odoo/addons/base_report_to_printer/i18n/pt_PT.po +937 -0
- odoo/addons/base_report_to_printer/i18n/sl.po +943 -0
- odoo/addons/base_report_to_printer/i18n/sv.po +983 -0
- odoo/addons/base_report_to_printer/i18n/tr.po +936 -0
- odoo/addons/base_report_to_printer/i18n/zh_CN.po +958 -0
- odoo/addons/base_report_to_printer/models/__init__.py +8 -0
- odoo/addons/base_report_to_printer/models/ir_actions_report.py +253 -0
- odoo/addons/base_report_to_printer/models/printing_action.py +26 -0
- odoo/addons/base_report_to_printer/models/printing_job.py +131 -0
- odoo/addons/base_report_to_printer/models/printing_printer.py +268 -0
- odoo/addons/base_report_to_printer/models/printing_report_xml_action.py +48 -0
- odoo/addons/base_report_to_printer/models/printing_server.py +275 -0
- odoo/addons/base_report_to_printer/models/printing_tray.py +21 -0
- odoo/addons/base_report_to_printer/models/res_users.py +55 -0
- odoo/addons/base_report_to_printer/readme/CONFIGURE.md +13 -0
- odoo/addons/base_report_to_printer/readme/CONTRIBUTORS.md +18 -0
- odoo/addons/base_report_to_printer/readme/CREDITS.md +1 -0
- odoo/addons/base_report_to_printer/readme/DESCRIPTION.md +27 -0
- odoo/addons/base_report_to_printer/readme/HISTORY.md +7 -0
- odoo/addons/base_report_to_printer/readme/INSTALL.md +10 -0
- odoo/addons/base_report_to_printer/readme/ROADMAP.md +3 -0
- odoo/addons/base_report_to_printer/readme/USAGE.md +15 -0
- odoo/addons/base_report_to_printer/security/ir.model.access.csv +2 -0
- odoo/addons/base_report_to_printer/security/security.xml +151 -0
- odoo/addons/base_report_to_printer/static/description/icon.png +0 -0
- odoo/addons/base_report_to_printer/static/description/index.html +561 -0
- odoo/addons/base_report_to_printer/static/src/js/qweb_action_manager.esm.js +92 -0
- odoo/addons/base_report_to_printer/tests/__init__.py +13 -0
- odoo/addons/base_report_to_printer/tests/test_ir_actions_report.py +350 -0
- odoo/addons/base_report_to_printer/tests/test_printing_job.py +70 -0
- odoo/addons/base_report_to_printer/tests/test_printing_printer.py +198 -0
- odoo/addons/base_report_to_printer/tests/test_printing_printer_tray.py +256 -0
- odoo/addons/base_report_to_printer/tests/test_printing_printer_wizard.py +94 -0
- odoo/addons/base_report_to_printer/tests/test_printing_report_xml_action.py +98 -0
- odoo/addons/base_report_to_printer/tests/test_printing_server.py +219 -0
- odoo/addons/base_report_to_printer/tests/test_printing_tray.py +49 -0
- odoo/addons/base_report_to_printer/tests/test_report.py +226 -0
- odoo/addons/base_report_to_printer/tests/test_res_users.py +53 -0
- odoo/addons/base_report_to_printer/views/ir_actions_report.xml +21 -0
- odoo/addons/base_report_to_printer/views/printing_job.xml +46 -0
- odoo/addons/base_report_to_printer/views/printing_printer.xml +147 -0
- odoo/addons/base_report_to_printer/views/printing_report.xml +39 -0
- odoo/addons/base_report_to_printer/views/printing_server.xml +79 -0
- odoo/addons/base_report_to_printer/views/res_users.xml +33 -0
- odoo/addons/base_report_to_printer/wizards/__init__.py +2 -0
- odoo/addons/base_report_to_printer/wizards/print_attachment_report.py +80 -0
- odoo/addons/base_report_to_printer/wizards/print_attachment_report.xml +56 -0
- odoo/addons/base_report_to_printer/wizards/printing_printer_update_wizard.py +27 -0
- odoo/addons/base_report_to_printer/wizards/printing_printer_update_wizard_view.xml +37 -0
- odoo_addon_base_report_to_printer-18.0.1.1.6.dist-info/METADATA +222 -0
- odoo_addon_base_report_to_printer-18.0.1.1.6.dist-info/RECORD +77 -0
- odoo_addon_base_report_to_printer-18.0.1.1.6.dist-info/WHEEL +5 -0
- odoo_addon_base_report_to_printer-18.0.1.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Copyright 2016 LasLabs Inc.
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
3
|
+
|
|
4
|
+
import errno
|
|
5
|
+
import tempfile
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from odoo.tests.common import TransactionCase
|
|
9
|
+
|
|
10
|
+
model = "odoo.addons.base_report_to_printer.models.printing_printer"
|
|
11
|
+
server_model = "odoo.addons.base_report_to_printer.models.printing_server"
|
|
12
|
+
|
|
13
|
+
ppd_header = '*PPD-Adobe: "4.3"'
|
|
14
|
+
ppd_input_slot_header = """
|
|
15
|
+
*OpenUI *InputSlot: PickOne
|
|
16
|
+
*DefaultInputSlot: Auto
|
|
17
|
+
*InputSlot Auto/Auto (Default): "
|
|
18
|
+
<< /DeferredMediaSelection true /ManualFeed false
|
|
19
|
+
/MediaPosition null /MediaType null >> setpagedevice
|
|
20
|
+
userdict /TSBMediaType 0 put"
|
|
21
|
+
*End
|
|
22
|
+
"""
|
|
23
|
+
ppd_input_slot_body = """
|
|
24
|
+
*InputSlot {name}/{text}: "
|
|
25
|
+
<< /DeferredMediaSelection true /ManualFeed false
|
|
26
|
+
/MediaPosition null /MediaType null >> setpagedevice
|
|
27
|
+
userdict /TSBMediaType 0 put"
|
|
28
|
+
*End
|
|
29
|
+
"""
|
|
30
|
+
ppd_input_slot_footer = """
|
|
31
|
+
*CloseUI: *InputSlot
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestPrintingPrinter(TransactionCase):
|
|
36
|
+
def setUp(self):
|
|
37
|
+
super().setUp()
|
|
38
|
+
self.Model = self.env["printing.printer"]
|
|
39
|
+
self.ServerModel = self.env["printing.server"]
|
|
40
|
+
self.server = self.env["printing.server"].create({})
|
|
41
|
+
self.printer = self.env["printing.printer"].create(
|
|
42
|
+
{
|
|
43
|
+
"name": "",
|
|
44
|
+
"server_id": self.server.id,
|
|
45
|
+
"system_name": "Sys Name",
|
|
46
|
+
"default": True,
|
|
47
|
+
"status": "unknown",
|
|
48
|
+
"status_message": "Msg",
|
|
49
|
+
"model": "res.users",
|
|
50
|
+
"location": "Location",
|
|
51
|
+
"uri": "URI",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
self.tray_vals = {
|
|
55
|
+
"name": "Tray",
|
|
56
|
+
"system_name": "TrayName",
|
|
57
|
+
"printer_id": self.printer.id,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def new_tray(self, vals=None):
|
|
61
|
+
values = self.tray_vals
|
|
62
|
+
if vals is not None:
|
|
63
|
+
values.update(vals)
|
|
64
|
+
return self.env["printing.tray"].create(values)
|
|
65
|
+
|
|
66
|
+
def build_ppd(self, input_slots=None):
|
|
67
|
+
"""
|
|
68
|
+
Builds a fake PPD file declaring defined input slots
|
|
69
|
+
"""
|
|
70
|
+
ppd_contents = ppd_header
|
|
71
|
+
ppd_contents += ppd_input_slot_header
|
|
72
|
+
if input_slots is not None:
|
|
73
|
+
for input_slot in input_slots:
|
|
74
|
+
ppd_contents += ppd_input_slot_body.format(
|
|
75
|
+
name=input_slot["name"], text=input_slot["text"]
|
|
76
|
+
)
|
|
77
|
+
ppd_contents += ppd_input_slot_footer
|
|
78
|
+
|
|
79
|
+
return ppd_contents
|
|
80
|
+
|
|
81
|
+
def mock_cups_ppd(self, cups, file_name=None, input_slots=None):
|
|
82
|
+
"""
|
|
83
|
+
Create a fake PPD file (if needed), then mock the getPPD3 method
|
|
84
|
+
return value to give that file
|
|
85
|
+
"""
|
|
86
|
+
if file_name is None:
|
|
87
|
+
fd, file_name = tempfile.mkstemp()
|
|
88
|
+
|
|
89
|
+
if file_name:
|
|
90
|
+
ppd_contents = self.build_ppd(input_slots=input_slots)
|
|
91
|
+
with open(file_name, "w") as fp:
|
|
92
|
+
fp.write(ppd_contents)
|
|
93
|
+
|
|
94
|
+
cups.Connection().getPPD3.return_value = (200, 0, file_name)
|
|
95
|
+
cups.Connection().getPrinters.return_value = {
|
|
96
|
+
self.printer.system_name: {
|
|
97
|
+
"printer-info": "info",
|
|
98
|
+
"printer-uri-supported": "uri",
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@mock.patch(f"{server_model}.cups")
|
|
103
|
+
def test_update_printers(self, cups):
|
|
104
|
+
"""
|
|
105
|
+
Check that the update_printers method calls _prepare_update_from_cups
|
|
106
|
+
"""
|
|
107
|
+
self.mock_cups_ppd(cups, file_name=False)
|
|
108
|
+
self.ServerModel.update_printers()
|
|
109
|
+
self.assertEqual(self.printer.name, "info")
|
|
110
|
+
self.printer.name = "My custom name"
|
|
111
|
+
self.ServerModel.update_printers()
|
|
112
|
+
self.assertEqual(self.printer.name, "My custom name")
|
|
113
|
+
|
|
114
|
+
@mock.patch(f"{server_model}.cups")
|
|
115
|
+
def test_prepare_update_from_cups_no_ppd(self, cups):
|
|
116
|
+
"""
|
|
117
|
+
Check that the tray_ids field has no value when no PPD is available
|
|
118
|
+
"""
|
|
119
|
+
self.mock_cups_ppd(cups, file_name=False)
|
|
120
|
+
|
|
121
|
+
connection = cups.Connection()
|
|
122
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
123
|
+
|
|
124
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
125
|
+
self.assertFalse("tray_ids" in vals)
|
|
126
|
+
|
|
127
|
+
@mock.patch(f"{server_model}.cups")
|
|
128
|
+
def test_prepare_update_from_cups_empty_ppd(self, cups):
|
|
129
|
+
"""
|
|
130
|
+
Check that the tray_ids field has no value when the PPD file has
|
|
131
|
+
no input slot declared
|
|
132
|
+
"""
|
|
133
|
+
fd, file_name = tempfile.mkstemp()
|
|
134
|
+
self.mock_cups_ppd(cups, file_name=file_name)
|
|
135
|
+
# Replace the ppd file's contents by an empty file
|
|
136
|
+
with open(file_name, "w") as fp:
|
|
137
|
+
fp.write(ppd_header)
|
|
138
|
+
|
|
139
|
+
connection = cups.Connection()
|
|
140
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
141
|
+
|
|
142
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
143
|
+
self.assertFalse("tray_ids" in vals)
|
|
144
|
+
|
|
145
|
+
@mock.patch(f"{server_model}.cups")
|
|
146
|
+
@mock.patch("os.unlink")
|
|
147
|
+
def test_prepare_update_from_cups_unlink_error(self, os_unlink, cups):
|
|
148
|
+
"""
|
|
149
|
+
When OSError other than ENOENT is encountered, the exception is raised
|
|
150
|
+
"""
|
|
151
|
+
# Break os.unlink
|
|
152
|
+
os_unlink.side_effect = OSError(errno.EIO, "Error")
|
|
153
|
+
|
|
154
|
+
self.mock_cups_ppd(cups)
|
|
155
|
+
|
|
156
|
+
connection = cups.Connection()
|
|
157
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
158
|
+
|
|
159
|
+
with self.assertRaises(OSError):
|
|
160
|
+
self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
161
|
+
|
|
162
|
+
@mock.patch(f"{server_model}.cups")
|
|
163
|
+
@mock.patch("os.unlink")
|
|
164
|
+
def test_prepare_update_from_cups_unlink_error_enoent(self, os_unlink, cups):
|
|
165
|
+
"""
|
|
166
|
+
When a ENOENT error is encountered, the file has already been unlinked
|
|
167
|
+
This is not an issue, as we were trying to delete the file.
|
|
168
|
+
The update can continue.
|
|
169
|
+
"""
|
|
170
|
+
# Break os.unlink
|
|
171
|
+
os_unlink.side_effect = OSError(errno.ENOENT, "Error")
|
|
172
|
+
|
|
173
|
+
self.mock_cups_ppd(cups)
|
|
174
|
+
|
|
175
|
+
connection = cups.Connection()
|
|
176
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
177
|
+
|
|
178
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
179
|
+
self.assertEqual(
|
|
180
|
+
vals["tray_ids"],
|
|
181
|
+
[(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})],
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@mock.patch(f"{server_model}.cups")
|
|
185
|
+
def test_prepare_update_from_cups(self, cups):
|
|
186
|
+
"""
|
|
187
|
+
Check the return value when adding a single tray
|
|
188
|
+
"""
|
|
189
|
+
self.mock_cups_ppd(cups)
|
|
190
|
+
|
|
191
|
+
connection = cups.Connection()
|
|
192
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
193
|
+
|
|
194
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
195
|
+
self.assertEqual(
|
|
196
|
+
vals["tray_ids"],
|
|
197
|
+
[(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@mock.patch(f"{server_model}.cups")
|
|
201
|
+
def test_prepare_update_from_cups_with_multiple_trays(self, cups):
|
|
202
|
+
"""
|
|
203
|
+
Check the return value when adding multiple trays at once
|
|
204
|
+
"""
|
|
205
|
+
self.mock_cups_ppd(cups, input_slots=[{"name": "Tray1", "text": "Tray 1"}])
|
|
206
|
+
|
|
207
|
+
connection = cups.Connection()
|
|
208
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
209
|
+
|
|
210
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
211
|
+
self.assertItemsEqual(
|
|
212
|
+
vals["tray_ids"],
|
|
213
|
+
[
|
|
214
|
+
(0, 0, {"name": "Auto (Default)", "system_name": "Auto"}),
|
|
215
|
+
(0, 0, {"name": "Tray 1", "system_name": "Tray1"}),
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@mock.patch(f"{server_model}.cups")
|
|
220
|
+
def test_prepare_update_from_cups_already_known_trays(self, cups):
|
|
221
|
+
"""
|
|
222
|
+
Check that calling the method twice doesn't create the trays multiple
|
|
223
|
+
times
|
|
224
|
+
"""
|
|
225
|
+
self.mock_cups_ppd(cups, input_slots=[{"name": "Tray1", "text": "Tray 1"}])
|
|
226
|
+
|
|
227
|
+
connection = cups.Connection()
|
|
228
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
229
|
+
|
|
230
|
+
# Create a tray which is in the PPD file
|
|
231
|
+
self.new_tray({"system_name": "Tray1"})
|
|
232
|
+
|
|
233
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
234
|
+
self.assertEqual(
|
|
235
|
+
vals["tray_ids"],
|
|
236
|
+
[(0, 0, {"name": "Auto (Default)", "system_name": "Auto"})],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
@mock.patch(f"{server_model}.cups")
|
|
240
|
+
def test_prepare_update_from_cups_unknown_trays(self, cups):
|
|
241
|
+
"""
|
|
242
|
+
Check that trays which are not in the PPD file are removed from Odoo
|
|
243
|
+
"""
|
|
244
|
+
self.mock_cups_ppd(cups)
|
|
245
|
+
|
|
246
|
+
connection = cups.Connection()
|
|
247
|
+
cups_printer = connection.getPrinters()[self.printer.system_name]
|
|
248
|
+
|
|
249
|
+
# Create a tray which is absent from the PPD file
|
|
250
|
+
tray = self.new_tray()
|
|
251
|
+
|
|
252
|
+
vals = self.printer._prepare_update_from_cups(connection, cups_printer)
|
|
253
|
+
self.assertEqual(
|
|
254
|
+
vals["tray_ids"],
|
|
255
|
+
[(0, 0, {"name": "Auto (Default)", "system_name": "Auto"}), (2, tray.id)],
|
|
256
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Copyright 2016 LasLabs Inc.
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from unittest import mock
|
|
6
|
+
|
|
7
|
+
from odoo.exceptions import UserError
|
|
8
|
+
from odoo.tests.common import TransactionCase
|
|
9
|
+
|
|
10
|
+
model = "odoo.addons.base_report_to_printer.models.printing_server"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StopTest(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestPrintingPrinterWizard(TransactionCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
super().setUp()
|
|
20
|
+
self.Model = self.env["printing.printer.update.wizard"]
|
|
21
|
+
self.server = self.env["printing.server"].create({})
|
|
22
|
+
self.printer_vals = {
|
|
23
|
+
"printer-info": "Info",
|
|
24
|
+
"printer-make-and-model": "Make and Model",
|
|
25
|
+
"printer-location": "location",
|
|
26
|
+
"device-uri": "URI",
|
|
27
|
+
"printer-uri-supported": "uri",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def _record_vals(self, sys_name="sys_name"):
|
|
31
|
+
return {
|
|
32
|
+
"name": self.printer_vals["printer-info"],
|
|
33
|
+
"server_id": self.server.id,
|
|
34
|
+
"system_name": sys_name,
|
|
35
|
+
"model": self.printer_vals["printer-make-and-model"],
|
|
36
|
+
"location": self.printer_vals["printer-location"],
|
|
37
|
+
"uri": self.printer_vals["device-uri"],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@mock.patch(f"{model}.cups")
|
|
41
|
+
def test_action_ok_inits_connection(self, cups):
|
|
42
|
+
"""It should initialize CUPS connection"""
|
|
43
|
+
self.Model.action_ok()
|
|
44
|
+
cups.Connection.assert_called_once_with(
|
|
45
|
+
host=self.server.address, port=self.server.port
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@mock.patch(f"{model}.cups")
|
|
49
|
+
def test_action_ok_gets_printers(self, cups):
|
|
50
|
+
"""It should get printers from CUPS"""
|
|
51
|
+
cups.Connection().getPrinters.return_value = {"sys_name": self.printer_vals}
|
|
52
|
+
cups.Connection().getPPD3.return_value = (200, 0, "")
|
|
53
|
+
self.Model.action_ok()
|
|
54
|
+
cups.Connection().getPrinters.assert_called_once_with()
|
|
55
|
+
|
|
56
|
+
def test_action_ok_raises_warning_on_error(self):
|
|
57
|
+
"""It should raise Warning on any error"""
|
|
58
|
+
with (
|
|
59
|
+
mock.patch(f"{model}.cups") as cups,
|
|
60
|
+
self.assertLogs(level=logging.WARNING) as logs,
|
|
61
|
+
):
|
|
62
|
+
cups.Connection.side_effect = StopTest
|
|
63
|
+
with self.assertRaises(UserError):
|
|
64
|
+
self.Model.action_ok()
|
|
65
|
+
self.assertEqual(len(logs.records), 1)
|
|
66
|
+
self.assertEqual(logs.records[0].levelno, logging.WARNING)
|
|
67
|
+
|
|
68
|
+
@mock.patch(f"{model}.cups")
|
|
69
|
+
def test_action_ok_creates_new_printer(self, cups):
|
|
70
|
+
"""It should create new printer w/ proper vals"""
|
|
71
|
+
cups.Connection().getPrinters.return_value = {"sys_name": self.printer_vals}
|
|
72
|
+
cups.Connection().getPPD3.return_value = (200, 0, "")
|
|
73
|
+
self.Model.action_ok()
|
|
74
|
+
rec_id = self.env["printing.printer"].search(
|
|
75
|
+
[("system_name", "=", "sys_name")], limit=1
|
|
76
|
+
)
|
|
77
|
+
self.assertTrue(rec_id)
|
|
78
|
+
for key, val in self._record_vals().items():
|
|
79
|
+
if rec_id._fields[key].type == "many2one":
|
|
80
|
+
val = self.env[rec_id._fields[key].comodel_name].browse(val)
|
|
81
|
+
|
|
82
|
+
self.assertEqual(val, rec_id[key])
|
|
83
|
+
|
|
84
|
+
@mock.patch(f"{model}.cups")
|
|
85
|
+
def test_action_ok_skips_existing_printer(self, cups):
|
|
86
|
+
"""It should not recreate existing printers"""
|
|
87
|
+
cups.Connection().getPrinters.return_value = {"sys_name": self.printer_vals}
|
|
88
|
+
cups.Connection().getPPD3.return_value = (200, 0, "")
|
|
89
|
+
self.env["printing.printer"].create(self._record_vals())
|
|
90
|
+
self.Model.action_ok()
|
|
91
|
+
res_ids = self.env["printing.printer"].search(
|
|
92
|
+
[("system_name", "=", "sys_name")]
|
|
93
|
+
)
|
|
94
|
+
self.assertEqual(1, len(res_ids))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright 2016 SYLEAM
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
3
|
+
|
|
4
|
+
from odoo.tests.common import TransactionCase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestPrintingReportXmlAction(TransactionCase):
|
|
8
|
+
def setUp(self):
|
|
9
|
+
super().setUp()
|
|
10
|
+
self.Model = self.env["printing.report.xml.action"]
|
|
11
|
+
|
|
12
|
+
self.report = self.env["ir.actions.report"].search([], limit=1)
|
|
13
|
+
self.server = self.env["printing.server"].create({})
|
|
14
|
+
|
|
15
|
+
self.report_vals = {
|
|
16
|
+
"report_id": self.report.id,
|
|
17
|
+
"user_id": self.env.ref("base.user_demo").id,
|
|
18
|
+
"action": "server",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def new_record(self, vals=None):
|
|
22
|
+
values = self.report_vals
|
|
23
|
+
if vals is not None:
|
|
24
|
+
values.update(vals)
|
|
25
|
+
|
|
26
|
+
return self.Model.create(values)
|
|
27
|
+
|
|
28
|
+
def new_printer(self):
|
|
29
|
+
return self.env["printing.printer"].create(
|
|
30
|
+
{
|
|
31
|
+
"name": "Printer",
|
|
32
|
+
"server_id": self.server.id,
|
|
33
|
+
"system_name": "Sys Name",
|
|
34
|
+
"default": True,
|
|
35
|
+
"status": "unknown",
|
|
36
|
+
"status_message": "Msg",
|
|
37
|
+
"model": "res.users",
|
|
38
|
+
"location": "Location",
|
|
39
|
+
"uri": "URI",
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def test_behaviour(self):
|
|
44
|
+
"""It should return some action's data, unless called on empty
|
|
45
|
+
recordset
|
|
46
|
+
"""
|
|
47
|
+
xml_action = self.new_record()
|
|
48
|
+
self.assertEqual(
|
|
49
|
+
xml_action.behaviour(),
|
|
50
|
+
{
|
|
51
|
+
"action": xml_action.action,
|
|
52
|
+
"printer": xml_action.printer_id,
|
|
53
|
+
"tray": False,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
xml_action = self.new_record({"printer_id": self.new_printer().id})
|
|
58
|
+
self.assertEqual(
|
|
59
|
+
xml_action.behaviour(),
|
|
60
|
+
{
|
|
61
|
+
"action": xml_action.action,
|
|
62
|
+
"printer": xml_action.printer_id,
|
|
63
|
+
"tray": False,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self.assertEqual(self.Model.behaviour(), {})
|
|
68
|
+
|
|
69
|
+
def test_onchange_printer_tray_id_empty(self):
|
|
70
|
+
action = self.env["printing.report.xml.action"].new({"printer_tray_id": False})
|
|
71
|
+
action.onchange_printer_id()
|
|
72
|
+
self.assertFalse(action.printer_tray_id)
|
|
73
|
+
|
|
74
|
+
def test_onchange_printer_tray_id_not_empty(self):
|
|
75
|
+
server = self.env["printing.server"].create({})
|
|
76
|
+
printer = self.env["printing.printer"].create(
|
|
77
|
+
{
|
|
78
|
+
"name": "Printer",
|
|
79
|
+
"server_id": server.id,
|
|
80
|
+
"system_name": "Sys Name",
|
|
81
|
+
"default": True,
|
|
82
|
+
"status": "unknown",
|
|
83
|
+
"status_message": "Msg",
|
|
84
|
+
"model": "res.users",
|
|
85
|
+
"location": "Location",
|
|
86
|
+
"uri": "URI",
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
tray = self.env["printing.tray"].create(
|
|
90
|
+
{"name": "Tray", "system_name": "TrayName", "printer_id": printer.id}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
action = self.env["printing.report.xml.action"].new(
|
|
94
|
+
{"printer_tray_id": tray.id}
|
|
95
|
+
)
|
|
96
|
+
self.assertEqual(action.printer_tray_id, tray)
|
|
97
|
+
action.onchange_printer_id()
|
|
98
|
+
self.assertFalse(action.printer_tray_id)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright 2016 LasLabs Inc.
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from unittest import mock
|
|
6
|
+
|
|
7
|
+
from odoo import fields
|
|
8
|
+
from odoo.tests.common import TransactionCase
|
|
9
|
+
|
|
10
|
+
model = "odoo.addons.base_report_to_printer.models.printing_server"
|
|
11
|
+
model_base = "odoo.models.BaseModel"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestPrintingServer(TransactionCase):
|
|
15
|
+
def setUp(self):
|
|
16
|
+
super().setUp()
|
|
17
|
+
self.Model = self.env["printing.server"]
|
|
18
|
+
self.server = self.Model.create({})
|
|
19
|
+
self.printer_vals = {
|
|
20
|
+
"name": "Printer",
|
|
21
|
+
"server_id": self.server.id,
|
|
22
|
+
"system_name": "Sys Name",
|
|
23
|
+
"default": True,
|
|
24
|
+
"status": "unknown",
|
|
25
|
+
"status_message": "Msg",
|
|
26
|
+
"model": "res.users",
|
|
27
|
+
"location": "Location",
|
|
28
|
+
"uri": "URI",
|
|
29
|
+
}
|
|
30
|
+
self.job_vals = {
|
|
31
|
+
"server_id": self.server.id,
|
|
32
|
+
"job_id_cups": 1,
|
|
33
|
+
"job_media_progress": 0,
|
|
34
|
+
"time_at_creation": fields.Datetime.now(),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def new_printer(self):
|
|
38
|
+
return self.env["printing.printer"].create(self.printer_vals)
|
|
39
|
+
|
|
40
|
+
def new_job(self, printer, vals=None):
|
|
41
|
+
values = self.job_vals
|
|
42
|
+
if vals is not None:
|
|
43
|
+
values.update(vals)
|
|
44
|
+
values["printer_id"] = printer.id
|
|
45
|
+
return self.env["printing.job"].create(values)
|
|
46
|
+
|
|
47
|
+
def test_update_printers_error(self):
|
|
48
|
+
"""It should catch any exception from CUPS and update status"""
|
|
49
|
+
with (
|
|
50
|
+
mock.patch(f"{model}.cups") as cups,
|
|
51
|
+
self.assertLogs(level=logging.WARNING) as logs,
|
|
52
|
+
):
|
|
53
|
+
cups.Connection.side_effect = Exception
|
|
54
|
+
rec_id = self.new_printer()
|
|
55
|
+
self.Model.update_printers()
|
|
56
|
+
self.assertEqual("server-error", rec_id.status)
|
|
57
|
+
self.assertEqual(len(logs.records), 1)
|
|
58
|
+
self.assertEqual(logs.records[0].levelno, logging.WARNING)
|
|
59
|
+
|
|
60
|
+
@mock.patch(f"{model}.cups")
|
|
61
|
+
def test_update_printers_inits_cups(self, cups):
|
|
62
|
+
"""It should init CUPS connection"""
|
|
63
|
+
self.new_printer()
|
|
64
|
+
self.Model.update_printers()
|
|
65
|
+
cups.Connection.assert_called_once_with(
|
|
66
|
+
host=self.server.address, port=self.server.port
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@mock.patch(f"{model}.cups")
|
|
70
|
+
def test_update_printers_gets_all_printers(self, cups):
|
|
71
|
+
"""It should get all printers from CUPS server"""
|
|
72
|
+
self.new_printer()
|
|
73
|
+
self.Model.update_printers()
|
|
74
|
+
cups.Connection().getPrinters.assert_called_once_with()
|
|
75
|
+
|
|
76
|
+
@mock.patch(f"{model}.cups")
|
|
77
|
+
def test_update_printers_search(self, cups):
|
|
78
|
+
"""It should search all when no domain"""
|
|
79
|
+
with mock.patch(f"{model_base}.search") as search:
|
|
80
|
+
self.Model.update_printers()
|
|
81
|
+
search.assert_called_once_with([])
|
|
82
|
+
|
|
83
|
+
@mock.patch(f"{model}.cups")
|
|
84
|
+
def test_update_printers_search_domain(self, cups):
|
|
85
|
+
"""It should use specific domain for search"""
|
|
86
|
+
with mock.patch(f"{model_base}.search") as search:
|
|
87
|
+
expect = [("id", ">", 0)]
|
|
88
|
+
self.Model.update_printers(expect)
|
|
89
|
+
search.assert_called_once_with(expect)
|
|
90
|
+
|
|
91
|
+
@mock.patch(f"{model}.cups")
|
|
92
|
+
def test_update_printers_update_unavailable(self, cups):
|
|
93
|
+
"""It should update status when printer is unavailable"""
|
|
94
|
+
rec_id = self.new_printer()
|
|
95
|
+
cups.Connection().getPrinters().get.return_value = False
|
|
96
|
+
self.Model.action_update_printers()
|
|
97
|
+
self.assertEqual("unavailable", rec_id.status)
|
|
98
|
+
|
|
99
|
+
@mock.patch(f"{model}.cups")
|
|
100
|
+
def test_update_archived_printers(self, cups):
|
|
101
|
+
"""It should update status even if printer is archived"""
|
|
102
|
+
rec_id = self.new_printer()
|
|
103
|
+
rec_id.toggle_active()
|
|
104
|
+
self.server.invalidate_model()
|
|
105
|
+
cups.Connection().getPrinters().get.return_value = False
|
|
106
|
+
self.Model.action_update_printers()
|
|
107
|
+
self.assertEqual(
|
|
108
|
+
"unavailable",
|
|
109
|
+
rec_id.status,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@mock.patch(f"{model}.cups")
|
|
113
|
+
def test_update_jobs_cron(self, cups):
|
|
114
|
+
"""It should get all jobs from CUPS server"""
|
|
115
|
+
self.new_printer()
|
|
116
|
+
self.Model.action_update_jobs()
|
|
117
|
+
cups.Connection().getPrinters.assert_called_once_with()
|
|
118
|
+
cups.Connection().getJobs.assert_called_once_with(
|
|
119
|
+
which_jobs="all",
|
|
120
|
+
first_job_id=-1,
|
|
121
|
+
requested_attributes=[
|
|
122
|
+
"job-name",
|
|
123
|
+
"job-id",
|
|
124
|
+
"printer-uri",
|
|
125
|
+
"job-media-progress",
|
|
126
|
+
"time-at-creation",
|
|
127
|
+
"job-state",
|
|
128
|
+
"job-state-reasons",
|
|
129
|
+
"time-at-processing",
|
|
130
|
+
"time-at-completed",
|
|
131
|
+
],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@mock.patch(f"{model}.cups")
|
|
135
|
+
def test_update_jobs_button(self, cups):
|
|
136
|
+
"""It should get all jobs from CUPS server"""
|
|
137
|
+
self.new_printer()
|
|
138
|
+
self.server.action_update_jobs()
|
|
139
|
+
cups.Connection().getPrinters.assert_called_once_with()
|
|
140
|
+
cups.Connection().getJobs.assert_called_once_with(
|
|
141
|
+
which_jobs="all",
|
|
142
|
+
first_job_id=-1,
|
|
143
|
+
requested_attributes=[
|
|
144
|
+
"job-name",
|
|
145
|
+
"job-id",
|
|
146
|
+
"printer-uri",
|
|
147
|
+
"job-media-progress",
|
|
148
|
+
"time-at-creation",
|
|
149
|
+
"job-state",
|
|
150
|
+
"job-state-reasons",
|
|
151
|
+
"time-at-processing",
|
|
152
|
+
"time-at-completed",
|
|
153
|
+
],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def test_update_jobs_error(self):
|
|
157
|
+
"""It should catch any exception from CUPS and update status"""
|
|
158
|
+
with (
|
|
159
|
+
mock.patch(f"{model}.cups") as cups,
|
|
160
|
+
self.assertLogs(level=logging.WARNING) as logs,
|
|
161
|
+
):
|
|
162
|
+
cups.Connection.side_effect = Exception
|
|
163
|
+
self.new_printer()
|
|
164
|
+
self.server.update_jobs()
|
|
165
|
+
cups.Connection.assert_called_with(
|
|
166
|
+
host=self.server.address, port=self.server.port
|
|
167
|
+
)
|
|
168
|
+
self.assertEqual(len(logs.records), 2)
|
|
169
|
+
self.assertEqual(logs.records[0].levelno, logging.WARNING)
|
|
170
|
+
|
|
171
|
+
@mock.patch(f"{model}.cups")
|
|
172
|
+
def test_update_jobs_uncompleted(self, cups):
|
|
173
|
+
"""
|
|
174
|
+
It should search which jobs have been completed since last update
|
|
175
|
+
"""
|
|
176
|
+
printer = self.new_printer()
|
|
177
|
+
self.new_job(printer, vals={"job_state": "completed"})
|
|
178
|
+
self.new_job(printer, vals={"job_id_cups": 2, "job_state": "processing"})
|
|
179
|
+
self.server.update_jobs(which="not-completed")
|
|
180
|
+
cups.Connection().getJobs.assert_any_call(
|
|
181
|
+
which_jobs="completed",
|
|
182
|
+
first_job_id=2,
|
|
183
|
+
requested_attributes=[
|
|
184
|
+
"job-name",
|
|
185
|
+
"job-id",
|
|
186
|
+
"printer-uri",
|
|
187
|
+
"job-media-progress",
|
|
188
|
+
"time-at-creation",
|
|
189
|
+
"job-state",
|
|
190
|
+
"job-state-reasons",
|
|
191
|
+
"time-at-processing",
|
|
192
|
+
"time-at-completed",
|
|
193
|
+
],
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
@mock.patch(f"{model}.cups")
|
|
197
|
+
def test_update_jobs(self, cups):
|
|
198
|
+
"""
|
|
199
|
+
It should update all jobs, known or not
|
|
200
|
+
"""
|
|
201
|
+
printer = self.new_printer()
|
|
202
|
+
printer_uri = "hostname:port/" + printer.system_name
|
|
203
|
+
cups.Connection().getJobs.return_value = {
|
|
204
|
+
1: {"printer-uri": printer_uri},
|
|
205
|
+
2: {"printer-uri": printer_uri, "job-state": 9},
|
|
206
|
+
4: {"printer-uri": printer_uri, "job-state": 5},
|
|
207
|
+
}
|
|
208
|
+
self.new_job(printer, vals={"job_state": "completed"})
|
|
209
|
+
completed_job = self.new_job(
|
|
210
|
+
printer, vals={"job_id_cups": 2, "job_state": "processing"}
|
|
211
|
+
)
|
|
212
|
+
purged_job = self.new_job(
|
|
213
|
+
printer, vals={"job_id_cups": 3, "job_state": "processing"}
|
|
214
|
+
)
|
|
215
|
+
self.server.update_jobs()
|
|
216
|
+
new_job = self.env["printing.job"].search([("job_id_cups", "=", 4)])
|
|
217
|
+
self.assertEqual(completed_job.job_state, "completed")
|
|
218
|
+
self.assertEqual(purged_job.active, False)
|
|
219
|
+
self.assertEqual(new_job.job_state, "processing")
|