1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
# -*- coding: utf-8 -*-
# Copyright 2014 Sébastien Alix
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
"""This module provide the :class:`Report` class to list available reports and
to generate/download them.
"""
import base64
import io
from odoorpc.tools import get_encodings, v
def encode2bytes(data):
for encoding in get_encodings():
try:
return data.decode(encoding)
except Exception:
pass
return data
class Report(object):
"""The `Report` class represents the report management service.
It provides methods to list and download available reports from the server.
.. note::
This service have to be used through the :attr:`odoorpc.ODOO.report`
property.
.. doctest::
:options: +SKIP
>>> import odoorpc
>>> odoo = odoorpc.ODOO('localhost', port=8069)
>>> odoo.login('odoorpc_test', 'admin', 'password')
>>> odoo.report
<odoorpc.report.Report object at 0x7f82fe7a1d50>
.. doctest::
:hide:
>>> import odoorpc
>>> odoo = odoorpc.ODOO(HOST, protocol=PROTOCOL, port=PORT)
>>> odoo.login(DB, USER, PWD)
>>> odoo.report
<odoorpc.report.Report object at ...>
"""
def __init__(self, odoo):
self._odoo = odoo
def download(self, name, ids, datas=None, context=None):
"""Download a report from the server and return it as a remote file.
Warning: this feature is not supported for Odoo >= 14 (CSRF token required).
For instance, to download the "Quotation / Order" report of sale orders
identified by the IDs ``[2, 3]``:
.. doctest::
:options: +SKIP
>>> report = odoo.report.download('sale.report_saleorder', [2, 3])
.. doctest::
:hide:
>>> from odoorpc.tools import v
>>> if v(VERSION) < v('14.0'):
... report = odoo.report.download('sale.report_saleorder', [2])
Write it on the file system:
.. doctest::
:options: +SKIP
>>> with open('sale_orders.pdf', 'wb') as report_file:
... report_file.write(report.read())
...
.. doctest::
:hide:
>>> from odoorpc.tools import v
>>> if v(VERSION) < v('14.0'):
... with open('sale_orders.pdf', 'wb') as report_file:
... fileno = report_file.write(report.read()) # Python 3
...
*Python 2:*
:return: `io.BytesIO`
:raise: :class:`odoorpc.error.RPCError` (wrong parameters)
:raise: `ValueError` (received invalid data)
:raise: `urllib2.URLError` (connection error)
*Python 3:*
:return: `io.BytesIO`
:raise: :class:`odoorpc.error.RPCError` (wrong parameters)
:raise: `ValueError` (received invalid data)
:raise: `urllib.error.URLError` (connection error)
"""
if context is None:
context = self._odoo.env.context
def check_report(name):
report_model = 'ir.actions.report'
if v(self._odoo.version)[0] < 11:
report_model = 'ir.actions.report.xml'
IrReport = self._odoo.env[report_model]
report_ids = IrReport.search([('report_name', '=', name)])
report_id = report_ids and report_ids[0] or False
if not report_id:
raise ValueError("The report '%s' does not exist." % name)
return report_id
report_id = check_report(name)
# Odoo >= 11.0
if v(self._odoo.version)[0] >= 11:
IrReport = self._odoo.env['ir.actions.report']
report = IrReport.browse(report_id)
if v(self._odoo.version)[0] >= 14:
# Need a CSRF token to print reports on Odoo >= 14
raise NotImplementedError
# response = report.with_context(context)._render(
# ids, data=datas
# )
else:
response = report.with_context(context).render(ids, data=datas)
content = response[0]
# On the server the result is a bytes string,
# but the RPC layer of Odoo returns it as a unicode string,
# so we encode it again as bytes
result = content.encode('latin1')
return io.BytesIO(result)
# Odoo < 11.0
else:
args_to_send = [
self._odoo.env.db,
self._odoo.env.uid,
self._odoo._password,
name,
ids,
datas,
context,
]
data = self._odoo.json(
'/jsonrpc',
{
'service': 'report',
'method': 'render_report',
'args': args_to_send,
},
)
if 'result' not in data and not data['result'].get('result'):
raise ValueError("Received invalid data.")
# Encode to bytes forced to be compatible with Python 3.2
# (its 'base64.standard_b64decode()' function only accepts bytes)
result = encode2bytes(data['result']['result'])
content = base64.standard_b64decode(result)
return io.BytesIO(content)
def list(self):
"""List available reports from the server.
It returns a dictionary with reports classified by data model:
.. doctest::
:options: +SKIP
>>> from odoorpc.tools import v
>>> inv_model = 'account.move'
>>> if v(VERSION) < v('13.0'):
... inv_model = 'account.invoice'
>>> odoo.report.list()[inv_model]
[{'name': u'Duplicates',
'report_name': u'account.account_invoice_report_duplicate_main',
'report_type': u'qweb-pdf'},
{'name': 'Invoices',
'report_type': 'qweb-pdf',
'report_name': 'account.report_invoice'}]
.. doctest::
:hide:
>>> from odoorpc.tools import v
>>> inv_model = 'account.move'
>>> if v(VERSION) < v('13.0'):
... inv_model = 'account.invoice'
>>> from pprint import pprint as pp
>>> any(data['report_name'] == 'account.report_invoice'
... for data in odoo.report.list()[inv_model])
True
*Python 2:*
:return: `list` of dictionaries
:raise: `urllib2.URLError` (connection error)
*Python 3:*
:return: `list` of dictionaries
:raise: `urllib.error.URLError` (connection error)
"""
report_model = 'ir.actions.report'
if v(self._odoo.version)[0] < 11:
report_model = 'ir.actions.report.xml'
IrReport = self._odoo.env[report_model]
report_ids = IrReport.search([])
reports = IrReport.read(
report_ids, ['name', 'model', 'report_name', 'report_type']
)
result = {}
for report in reports:
model = report.pop('model')
report.pop('id')
if model not in result:
result[model] = []
result[model].append(report)
return result
|