File: report.py

package info (click to toggle)
python-odoorpc 0.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 604 kB
  • sloc: python: 3,461; makefile: 154; sh: 36
file content (223 lines) | stat: -rw-r--r-- 7,396 bytes parent folder | download
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