PyOdoo 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PKG-INFO +6 -2
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PyOdoo.egg-info/PKG-INFO +6 -2
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PyOdoo.egg-info/SOURCES.txt +1 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/README.md +3 -1
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/api.py +76 -8
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/constants.py +1 -1
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/implementation.py +1 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/model.py +7 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/contacts.py +1 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/contacts_async.py +1 -0
- pyodoo-0.9.0/pyodoo/transport.py +78 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyproject.toml +2 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/tests/test_compare_types.py +53 -40
- {pyodoo-0.8.0 → pyodoo-0.9.0}/tests/test_contacts.py +25 -6
- {pyodoo-0.8.0 → pyodoo-0.9.0}/tests/test_python_code.py +28 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/tests/test_sql_excel_query.py +29 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/LICENSE +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PyOdoo.egg-info/dependency_links.txt +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PyOdoo.egg-info/requires.txt +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/PyOdoo.egg-info/top_level.txt +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/__init__.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/active_status_choice.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/boolean_operator.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/compare_type.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/filter.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/message_subtype.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/python_code.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/__init__.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/awaitable.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/python_code.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/samples/sql_excel_query.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/sql_excel_query.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/pyodoo/v12/__init__.py +0 -0
- {pyodoo-0.8.0 → pyodoo-0.9.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyOdoo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: API for Odoo
|
|
5
5
|
Author-email: Fabio Castelli <muflone@muflone.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -12,11 +12,13 @@ Classifier: Development Status :: 1 - Planning
|
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Requires-Python: >=3.9
|
|
20
22
|
Description-Content-Type: text/markdown
|
|
21
23
|
License-File: LICENSE
|
|
22
24
|
Requires-Dist: xlrd~=2.0
|
|
@@ -25,7 +27,6 @@ Dynamic: license-file
|
|
|
25
27
|
|
|
26
28
|
# PyOdoo
|
|
27
29
|
|
|
28
|
-
[](https://www.travis-ci.com/github/muflone/pyodoo)
|
|
29
30
|
[](https://circleci.com/gh/muflone/pyodoo)
|
|
30
31
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
|
|
31
32
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
|
|
@@ -55,6 +56,7 @@ its APIs.
|
|
|
55
56
|
Currently the following implementations are provided:
|
|
56
57
|
- XML-RPC
|
|
57
58
|
- JSON-RPC
|
|
59
|
+
- WEB-RPC
|
|
58
60
|
|
|
59
61
|
# System Requirements
|
|
60
62
|
|
|
@@ -65,3 +67,5 @@ Currently the following implementations are provided:
|
|
|
65
67
|
# Usage
|
|
66
68
|
|
|
67
69
|
Please see the **samples** and the **tests** folders for some usage examples.
|
|
70
|
+
|
|
71
|
+
Examples and documentation are available at <http://www.muflone.com/pyodoo/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyOdoo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: API for Odoo
|
|
5
5
|
Author-email: Fabio Castelli <muflone@muflone.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -12,11 +12,13 @@ Classifier: Development Status :: 1 - Planning
|
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Requires-Python: >=3.9
|
|
20
22
|
Description-Content-Type: text/markdown
|
|
21
23
|
License-File: LICENSE
|
|
22
24
|
Requires-Dist: xlrd~=2.0
|
|
@@ -25,7 +27,6 @@ Dynamic: license-file
|
|
|
25
27
|
|
|
26
28
|
# PyOdoo
|
|
27
29
|
|
|
28
|
-
[](https://www.travis-ci.com/github/muflone/pyodoo)
|
|
29
30
|
[](https://circleci.com/gh/muflone/pyodoo)
|
|
30
31
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
|
|
31
32
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
|
|
@@ -55,6 +56,7 @@ its APIs.
|
|
|
55
56
|
Currently the following implementations are provided:
|
|
56
57
|
- XML-RPC
|
|
57
58
|
- JSON-RPC
|
|
59
|
+
- WEB-RPC
|
|
58
60
|
|
|
59
61
|
# System Requirements
|
|
60
62
|
|
|
@@ -65,3 +67,5 @@ Currently the following implementations are provided:
|
|
|
65
67
|
# Usage
|
|
66
68
|
|
|
67
69
|
Please see the **samples** and the **tests** folders for some usage examples.
|
|
70
|
+
|
|
71
|
+
Examples and documentation are available at <http://www.muflone.com/pyodoo/>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# PyOdoo
|
|
2
2
|
|
|
3
|
-
[](https://www.travis-ci.com/github/muflone/pyodoo)
|
|
4
3
|
[](https://circleci.com/gh/muflone/pyodoo)
|
|
5
4
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
|
|
6
5
|
[](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
|
|
@@ -30,6 +29,7 @@ its APIs.
|
|
|
30
29
|
Currently the following implementations are provided:
|
|
31
30
|
- XML-RPC
|
|
32
31
|
- JSON-RPC
|
|
32
|
+
- WEB-RPC
|
|
33
33
|
|
|
34
34
|
# System Requirements
|
|
35
35
|
|
|
@@ -40,3 +40,5 @@ Currently the following implementations are provided:
|
|
|
40
40
|
# Usage
|
|
41
41
|
|
|
42
42
|
Please see the **samples** and the **tests** folders for some usage examples.
|
|
43
|
+
|
|
44
|
+
Examples and documentation are available at <http://www.muflone.com/pyodoo/>
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
##
|
|
20
20
|
|
|
21
21
|
from typing import Any, Optional, Union
|
|
22
|
+
import urllib.parse
|
|
22
23
|
from xmlrpc.client import ServerProxy
|
|
23
24
|
|
|
24
25
|
import requests
|
|
@@ -27,6 +28,7 @@ import requests.auth
|
|
|
27
28
|
from .boolean_operator import BooleanOperator
|
|
28
29
|
from .filter import Filter
|
|
29
30
|
from .implementation import Implementation
|
|
31
|
+
from .transport import TimeoutHttpTransport, TimeoutHttpsTransport
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class Api(object):
|
|
@@ -53,11 +55,13 @@ class Api(object):
|
|
|
53
55
|
self.language = language
|
|
54
56
|
self.uid = None
|
|
55
57
|
self.implementation = implementation
|
|
58
|
+
self.timeout = None
|
|
56
59
|
# Remove trailing / from endpoint
|
|
57
60
|
if self.endpoint.endswith('/'):
|
|
58
61
|
self.endpoint = self.endpoint[:-1]
|
|
59
62
|
# Specific implementations
|
|
60
|
-
if implementation
|
|
63
|
+
if implementation in (Implementation.JSONRPC,
|
|
64
|
+
Implementation.WEBRPC):
|
|
61
65
|
self.session = requests.Session()
|
|
62
66
|
|
|
63
67
|
def authenticate(self
|
|
@@ -76,19 +80,39 @@ class Api(object):
|
|
|
76
80
|
{})
|
|
77
81
|
elif self.implementation == Implementation.JSONRPC:
|
|
78
82
|
# Authentication for JSON-RPC
|
|
79
|
-
self.session.auth = requests.auth.HTTPBasicAuth(
|
|
80
|
-
username=self.username,
|
|
81
|
-
password=self.password)
|
|
82
83
|
response = self.session.post(
|
|
83
|
-
url=self.build_endpoint('
|
|
84
|
+
url=self.build_endpoint(method='jsonrpc'),
|
|
85
|
+
json={
|
|
86
|
+
'jsonrpc': '2.0',
|
|
87
|
+
'method': 'call',
|
|
88
|
+
'params': {
|
|
89
|
+
'service': 'common',
|
|
90
|
+
'method': 'authenticate',
|
|
91
|
+
'args': [
|
|
92
|
+
self.database,
|
|
93
|
+
self.username,
|
|
94
|
+
self.password,
|
|
95
|
+
{}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
timeout=self.timeout)
|
|
100
|
+
result = response.json() if response else None
|
|
101
|
+
self.uid = result['result'] if result else None
|
|
102
|
+
elif self.implementation == Implementation.WEBRPC:
|
|
103
|
+
# Authentication for WEB-RPC
|
|
104
|
+
response = self.session.post(
|
|
105
|
+
url=self.build_endpoint(method='web/session/authenticate'),
|
|
84
106
|
json={
|
|
85
107
|
'jsonrpc': '2.0',
|
|
108
|
+
'method': 'call',
|
|
86
109
|
'params': {
|
|
87
110
|
'db': self.database,
|
|
88
111
|
'login': self.username,
|
|
89
112
|
'password': self.password
|
|
90
113
|
}
|
|
91
|
-
}
|
|
114
|
+
},
|
|
115
|
+
timeout=self.timeout)
|
|
92
116
|
result = response.json() if response else None
|
|
93
117
|
self.uid = result['result']['uid'] if result else None
|
|
94
118
|
else:
|
|
@@ -117,7 +141,22 @@ class Api(object):
|
|
|
117
141
|
:param method: The name of the method to execute
|
|
118
142
|
:return: A new ServerProxy object
|
|
119
143
|
"""
|
|
120
|
-
|
|
144
|
+
url = self.build_endpoint(method=method)
|
|
145
|
+
scheme = urllib.parse.urlparse(url=url).scheme.lower()
|
|
146
|
+
if self.timeout is not None and scheme == 'https':
|
|
147
|
+
# Return a ServerProxy object using TimeoutHttpsTransport
|
|
148
|
+
result = ServerProxy(
|
|
149
|
+
uri=url,
|
|
150
|
+
transport=TimeoutHttpsTransport(timeout=self.timeout))
|
|
151
|
+
elif self.timeout is not None and scheme == 'http':
|
|
152
|
+
# Return a ServerProxy object using TimeoutHttpTransport
|
|
153
|
+
result = ServerProxy(
|
|
154
|
+
uri=url,
|
|
155
|
+
transport=TimeoutHttpTransport(timeout=self.timeout))
|
|
156
|
+
else:
|
|
157
|
+
# Return the standard ServerProxy object without any transport
|
|
158
|
+
result = ServerProxy(uri=url)
|
|
159
|
+
return result
|
|
121
160
|
|
|
122
161
|
def get_proxy_object(self
|
|
123
162
|
) -> ServerProxy:
|
|
@@ -184,7 +223,26 @@ class Api(object):
|
|
|
184
223
|
kwargs
|
|
185
224
|
],
|
|
186
225
|
}
|
|
187
|
-
}
|
|
226
|
+
},
|
|
227
|
+
timeout=self.timeout
|
|
228
|
+
).json().get('result')
|
|
229
|
+
elif self.implementation == Implementation.WEBRPC:
|
|
230
|
+
# Execute for WEB-RPC
|
|
231
|
+
results = self.session.post(
|
|
232
|
+
url=self.build_endpoint(method='web/dataset/call_kw/'
|
|
233
|
+
f'{self.model_name}/'
|
|
234
|
+
f'{method_name}'),
|
|
235
|
+
json={
|
|
236
|
+
'jsonrpc': '2.0',
|
|
237
|
+
'method': 'call',
|
|
238
|
+
'params': {
|
|
239
|
+
'model': self.model_name,
|
|
240
|
+
'method': method_name,
|
|
241
|
+
'args': args,
|
|
242
|
+
'kwargs': kwargs
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
timeout=self.timeout
|
|
188
246
|
).json().get('result')
|
|
189
247
|
else:
|
|
190
248
|
# Invalid implementation
|
|
@@ -368,3 +426,13 @@ class Api(object):
|
|
|
368
426
|
return self.do_execute(method_name='message_post',
|
|
369
427
|
args=[entity_id],
|
|
370
428
|
kwargs=kwargs)
|
|
429
|
+
|
|
430
|
+
def set_timeout(self,
|
|
431
|
+
timeout: Optional[float]) -> None:
|
|
432
|
+
"""
|
|
433
|
+
Set the connection and transfer timeout
|
|
434
|
+
|
|
435
|
+
:param timeout: allowed time for connection and transfer in seconds
|
|
436
|
+
:return: None
|
|
437
|
+
"""
|
|
438
|
+
self.timeout = timeout
|
|
@@ -263,9 +263,16 @@ class Model(object):
|
|
|
263
263
|
language=self.api.language,
|
|
264
264
|
authenticate=authenticate,
|
|
265
265
|
implementation=self.api.implementation)
|
|
266
|
+
# Use the same timeout
|
|
267
|
+
if self.api.timeout is not None:
|
|
268
|
+
model.api.set_timeout(timeout=self.api.timeout)
|
|
266
269
|
if not authenticate and use_existing_uid:
|
|
267
270
|
# Use the existing UID
|
|
268
271
|
model.api.uid = self.api.uid
|
|
272
|
+
if model.api.implementation in (Implementation.JSONRPC,
|
|
273
|
+
Implementation.WEBRPC):
|
|
274
|
+
# Use the same session
|
|
275
|
+
model.api.session = self.api.session
|
|
269
276
|
return model
|
|
270
277
|
|
|
271
278
|
@_ignore_none_errors
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Project: PyOdoo
|
|
3
|
+
# Description: API for Odoo
|
|
4
|
+
# Author: Fabio Castelli (Muflone) <muflone@muflone.com>
|
|
5
|
+
# Copyright: 2021-2026 Fabio Castelli
|
|
6
|
+
# License: GPL-3+
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
##
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
from xmlrpc.client import SafeTransport, Transport
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TimeoutHttpTransport(Transport):
|
|
26
|
+
"""
|
|
27
|
+
XML-RPC transport with configurable timeout for HTTP endpoints.
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self,
|
|
30
|
+
timeout: float,
|
|
31
|
+
*args: Any,
|
|
32
|
+
**kwargs: Any
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(*args, **kwargs)
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
|
|
37
|
+
def make_connection(self,
|
|
38
|
+
host: str
|
|
39
|
+
) -> Any:
|
|
40
|
+
"""
|
|
41
|
+
Custom make_connection method to set the socket timeout
|
|
42
|
+
|
|
43
|
+
:param host: destination host
|
|
44
|
+
:return: connection object
|
|
45
|
+
"""
|
|
46
|
+
connection = super().make_connection(host=host)
|
|
47
|
+
connection.timeout = self.timeout
|
|
48
|
+
if connection.sock:
|
|
49
|
+
connection.sock.settimeout(self.timeout)
|
|
50
|
+
return connection
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TimeoutHttpsTransport(SafeTransport):
|
|
54
|
+
"""
|
|
55
|
+
XML-RPC transport with configurable timeout for HTTPS endpoints.
|
|
56
|
+
"""
|
|
57
|
+
def __init__(self,
|
|
58
|
+
timeout: float,
|
|
59
|
+
*args: Any,
|
|
60
|
+
**kwargs: Any
|
|
61
|
+
) -> None:
|
|
62
|
+
super().__init__(*args, **kwargs)
|
|
63
|
+
self.timeout = timeout
|
|
64
|
+
|
|
65
|
+
def make_connection(self,
|
|
66
|
+
host: str
|
|
67
|
+
) -> Any:
|
|
68
|
+
"""
|
|
69
|
+
Custom make_connection method to set the socket timeout
|
|
70
|
+
|
|
71
|
+
:param host: destination host
|
|
72
|
+
:return: connection object
|
|
73
|
+
"""
|
|
74
|
+
connection = super().make_connection(host=host)
|
|
75
|
+
connection.timeout = self.timeout
|
|
76
|
+
if connection.sock:
|
|
77
|
+
connection.sock.settimeout(self.timeout)
|
|
78
|
+
return connection
|
|
@@ -12,6 +12,7 @@ authors = [
|
|
|
12
12
|
license = 'GPL-3.0-or-later'
|
|
13
13
|
license-files = ['LICENSE']
|
|
14
14
|
dynamic = ['version']
|
|
15
|
+
requires-python = '>=3.9'
|
|
15
16
|
dependencies = [
|
|
16
17
|
'xlrd~=2.0',
|
|
17
18
|
'requests~=2.32',
|
|
@@ -21,6 +22,7 @@ classifiers = [
|
|
|
21
22
|
'Intended Audience :: Developers',
|
|
22
23
|
'Operating System :: OS Independent',
|
|
23
24
|
'Programming Language :: Python :: 3',
|
|
25
|
+
'Programming Language :: Python :: 3.9',
|
|
24
26
|
'Programming Language :: Python :: 3.10',
|
|
25
27
|
'Programming Language :: Python :: 3.11',
|
|
26
28
|
'Programming Language :: Python :: 3.12',
|
|
@@ -56,8 +56,8 @@ class BaseTests():
|
|
|
56
56
|
results = self.model.search(filters=filters)
|
|
57
57
|
# Check if the results are not None
|
|
58
58
|
self.assertIsNotNone(results)
|
|
59
|
-
# Check if the results list has
|
|
60
|
-
self.
|
|
59
|
+
# Check if the results list has at least one item
|
|
60
|
+
self.assertGreaterEqual(len(results), 1)
|
|
61
61
|
# Check if the results list doesn't contain the Office Chair Black
|
|
62
62
|
self.assertNotIn(17, results)
|
|
63
63
|
|
|
@@ -75,8 +75,8 @@ class BaseTests():
|
|
|
75
75
|
results = self.model.search(filters=filters)
|
|
76
76
|
# Check if the results are not None
|
|
77
77
|
self.assertIsNotNone(results)
|
|
78
|
-
# Check if the results list has
|
|
79
|
-
self.
|
|
78
|
+
# Check if the results list has at least one item
|
|
79
|
+
self.assertGreaterEqual(len(results), 1)
|
|
80
80
|
# Check if the results list doesn't contain the Office Chair Black
|
|
81
81
|
self.assertNotIn(17, results)
|
|
82
82
|
|
|
@@ -94,8 +94,8 @@ class BaseTests():
|
|
|
94
94
|
results = self.model.search(filters=filters)
|
|
95
95
|
# Check if the results are not None
|
|
96
96
|
self.assertIsNotNone(results)
|
|
97
|
-
# Check if the results list has
|
|
98
|
-
self.
|
|
97
|
+
# Check if the results list has at least one item
|
|
98
|
+
self.assertGreaterEqual(len(results), 1)
|
|
99
99
|
# Check if the results list contains the Office Chair Black
|
|
100
100
|
self.assertIn(17, results)
|
|
101
101
|
|
|
@@ -113,8 +113,8 @@ class BaseTests():
|
|
|
113
113
|
results = self.model.search(filters=filters)
|
|
114
114
|
# Check if the results are not None
|
|
115
115
|
self.assertIsNotNone(results)
|
|
116
|
-
# Check if the results list has
|
|
117
|
-
self.
|
|
116
|
+
# Check if the results list has at least one item
|
|
117
|
+
self.assertGreaterEqual(len(results), 1)
|
|
118
118
|
# Check if the results list doesn't contain the Office Chair Black
|
|
119
119
|
self.assertNotIn(17, results)
|
|
120
120
|
|
|
@@ -132,8 +132,8 @@ class BaseTests():
|
|
|
132
132
|
results = self.model.search(filters=filters)
|
|
133
133
|
# Check if the results are not None
|
|
134
134
|
self.assertIsNotNone(results)
|
|
135
|
-
# Check if the results list has
|
|
136
|
-
self.
|
|
135
|
+
# Check if the results list has at least one item
|
|
136
|
+
self.assertGreaterEqual(len(results), 1)
|
|
137
137
|
# Check if the results list contains the Office Chair Black
|
|
138
138
|
self.assertIn(17, results)
|
|
139
139
|
|
|
@@ -152,8 +152,8 @@ class BaseTests():
|
|
|
152
152
|
results = self.model.search(filters=filters)
|
|
153
153
|
# Check if the results are not None
|
|
154
154
|
self.assertIsNotNone(results)
|
|
155
|
-
# Check if the results list has
|
|
156
|
-
self.
|
|
155
|
+
# Check if the results list has at least one item
|
|
156
|
+
self.assertGreaterEqual(len(results), 1)
|
|
157
157
|
# Check if the results list contains the Office Design Software
|
|
158
158
|
self.assertIn(7, results)
|
|
159
159
|
# Check if the results list contains the Office Chair Black
|
|
@@ -174,8 +174,8 @@ class BaseTests():
|
|
|
174
174
|
results = self.model.search(filters=filters)
|
|
175
175
|
# Check if the results are not None
|
|
176
176
|
self.assertIsNotNone(results)
|
|
177
|
-
# Check if the results list has
|
|
178
|
-
self.
|
|
177
|
+
# Check if the results list has at least one item
|
|
178
|
+
self.assertGreaterEqual(len(results), 1)
|
|
179
179
|
# Check if the results list doesn't contain the Office Design Software
|
|
180
180
|
self.assertNotIn(7, results)
|
|
181
181
|
# Check if the results list doesn't contain the Office Chair Black
|
|
@@ -192,8 +192,8 @@ class BaseTests():
|
|
|
192
192
|
results = self.model.search(filters=filters)
|
|
193
193
|
# Check if the results are not None
|
|
194
194
|
self.assertIsNotNone(results)
|
|
195
|
-
# Check if the results list has
|
|
196
|
-
self.
|
|
195
|
+
# Check if the results list has at least one item
|
|
196
|
+
self.assertGreaterEqual(len(results), 1)
|
|
197
197
|
# Check if the results list contains the Office Chair Black
|
|
198
198
|
self.assertIn(17, results)
|
|
199
199
|
|
|
@@ -211,8 +211,8 @@ class BaseTests():
|
|
|
211
211
|
results = self.model.search(filters=filters)
|
|
212
212
|
# Check if the results are not None
|
|
213
213
|
self.assertIsNotNone(results)
|
|
214
|
-
# Check if the results list has
|
|
215
|
-
self.
|
|
214
|
+
# Check if the results list has at least one item
|
|
215
|
+
self.assertGreaterEqual(len(results), 1)
|
|
216
216
|
# Check if the results list doesn't contain the Office Chair Black
|
|
217
217
|
self.assertNotIn(17, results)
|
|
218
218
|
|
|
@@ -230,8 +230,8 @@ class BaseTests():
|
|
|
230
230
|
results = self.model.search(filters=filters)
|
|
231
231
|
# Check if the results are not None
|
|
232
232
|
self.assertIsNotNone(results)
|
|
233
|
-
# Check if the results list has
|
|
234
|
-
self.
|
|
233
|
+
# Check if the results list has at least one item
|
|
234
|
+
self.assertGreaterEqual(len(results), 1)
|
|
235
235
|
# Check if the results list doesn't contain the Office Chair Black
|
|
236
236
|
self.assertNotIn(17, results)
|
|
237
237
|
|
|
@@ -249,8 +249,8 @@ class BaseTests():
|
|
|
249
249
|
results = self.model.search(filters=filters)
|
|
250
250
|
# Check if the results are not None
|
|
251
251
|
self.assertIsNotNone(results)
|
|
252
|
-
# Check if the results list has
|
|
253
|
-
self.
|
|
252
|
+
# Check if the results list has at least one item
|
|
253
|
+
self.assertGreaterEqual(len(results), 1)
|
|
254
254
|
# Check if the results list contains the Office Chair Black
|
|
255
255
|
self.assertIn(17, results)
|
|
256
256
|
|
|
@@ -268,8 +268,8 @@ class BaseTests():
|
|
|
268
268
|
results = self.model.search(filters=filters)
|
|
269
269
|
# Check if the results are not None
|
|
270
270
|
self.assertIsNotNone(results)
|
|
271
|
-
# Check if the results list has
|
|
272
|
-
self.
|
|
271
|
+
# Check if the results list has at least one item
|
|
272
|
+
self.assertGreaterEqual(len(results), 1)
|
|
273
273
|
# Check if the results list doesn't contain the Office Chair Black
|
|
274
274
|
self.assertNotIn(17, results)
|
|
275
275
|
|
|
@@ -287,8 +287,8 @@ class BaseTests():
|
|
|
287
287
|
results = self.model.search(filters=filters)
|
|
288
288
|
# Check if the results are not None
|
|
289
289
|
self.assertIsNotNone(results)
|
|
290
|
-
# Check if the results list has
|
|
291
|
-
self.
|
|
290
|
+
# Check if the results list has at least one item
|
|
291
|
+
self.assertGreaterEqual(len(results), 1)
|
|
292
292
|
# Check if the results list contains the Office Chair Black
|
|
293
293
|
self.assertIn(17, results)
|
|
294
294
|
|
|
@@ -306,8 +306,8 @@ class BaseTests():
|
|
|
306
306
|
results = self.model.search(filters=filters)
|
|
307
307
|
# Check if the results are not None
|
|
308
308
|
self.assertIsNotNone(results)
|
|
309
|
-
# Check if the results list has
|
|
310
|
-
self.
|
|
309
|
+
# Check if the results list has at least one item
|
|
310
|
+
self.assertGreaterEqual(len(results), 1)
|
|
311
311
|
# Check if the results list doesn't contain the Office Chair Black
|
|
312
312
|
self.assertNotIn(17, results)
|
|
313
313
|
|
|
@@ -325,8 +325,8 @@ class BaseTests():
|
|
|
325
325
|
results = self.model.search(filters=filters)
|
|
326
326
|
# Check if the results are not None
|
|
327
327
|
self.assertIsNotNone(results)
|
|
328
|
-
# Check if the results list has
|
|
329
|
-
self.
|
|
328
|
+
# Check if the results list has at least one item
|
|
329
|
+
self.assertGreaterEqual(len(results), 1)
|
|
330
330
|
# Check if the results list contains the Office Chair Black
|
|
331
331
|
self.assertIn(17, results)
|
|
332
332
|
|
|
@@ -344,8 +344,8 @@ class BaseTests():
|
|
|
344
344
|
results = self.model.search(filters=filters)
|
|
345
345
|
# Check if the results are not None
|
|
346
346
|
self.assertIsNotNone(results)
|
|
347
|
-
# Check if the results list has
|
|
348
|
-
self.
|
|
347
|
+
# Check if the results list has at least one item
|
|
348
|
+
self.assertGreaterEqual(len(results), 1)
|
|
349
349
|
# Check if the results list contains the Office Chair Black
|
|
350
350
|
self.assertIn(17, results)
|
|
351
351
|
|
|
@@ -363,8 +363,8 @@ class BaseTests():
|
|
|
363
363
|
results = self.model.search(filters=filters)
|
|
364
364
|
# Check if the results are not None
|
|
365
365
|
self.assertIsNotNone(results)
|
|
366
|
-
# Check if the results list has
|
|
367
|
-
self.
|
|
366
|
+
# Check if the results list has at least one item
|
|
367
|
+
self.assertGreaterEqual(len(results), 1)
|
|
368
368
|
# Check if the results list contains the Office Chair Black
|
|
369
369
|
self.assertIn(17, results)
|
|
370
370
|
|
|
@@ -378,12 +378,12 @@ class BaseTests():
|
|
|
378
378
|
value='Office'),
|
|
379
379
|
Filter(field='categ_id',
|
|
380
380
|
compare_type=CompareType.CHILD_OF,
|
|
381
|
-
value='
|
|
381
|
+
value='Furniture')]
|
|
382
382
|
results = self.model.search(filters=filters)
|
|
383
383
|
# Check if the results are not None
|
|
384
384
|
self.assertIsNotNone(results)
|
|
385
|
-
# Check if the results list has
|
|
386
|
-
self.
|
|
385
|
+
# Check if the results list has at least one item
|
|
386
|
+
self.assertGreaterEqual(len(results), 1)
|
|
387
387
|
# Check if the results list contains the Office Chair Black
|
|
388
388
|
self.assertIn(17, results)
|
|
389
389
|
|
|
@@ -397,12 +397,12 @@ class BaseTests():
|
|
|
397
397
|
value='Office'),
|
|
398
398
|
Filter(field='categ_id',
|
|
399
399
|
compare_type=CompareType.PARENT_OF,
|
|
400
|
-
value='
|
|
400
|
+
value='Furniture')]
|
|
401
401
|
results = self.model.search(filters=filters)
|
|
402
402
|
# Check if the results are not None
|
|
403
403
|
self.assertIsNotNone(results)
|
|
404
|
-
# Check if the results list has
|
|
405
|
-
self.
|
|
404
|
+
# Check if the results list has at least one item
|
|
405
|
+
self.assertGreaterEqual(len(results), 1)
|
|
406
406
|
# Check if the results list contains the Office Chair Black
|
|
407
407
|
self.assertIn(17, results)
|
|
408
408
|
|
|
@@ -431,3 +431,16 @@ class TestCasesJsonRpc(BaseTests, unittest.TestCase):
|
|
|
431
431
|
model_name='product.template',
|
|
432
432
|
implementation=Implementation.JSONRPC)
|
|
433
433
|
cls.model.authenticate()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class TestCasesWebRpc(BaseTests, unittest.TestCase):
|
|
437
|
+
@classmethod
|
|
438
|
+
def setUpClass(cls
|
|
439
|
+
) -> None:
|
|
440
|
+
"""
|
|
441
|
+
Model object preparation
|
|
442
|
+
"""
|
|
443
|
+
cls.model = utility.get_model_from_demo(
|
|
444
|
+
model_name='product.template',
|
|
445
|
+
implementation=Implementation.WEBRPC)
|
|
446
|
+
cls.model.authenticate()
|
|
@@ -135,7 +135,7 @@ class BaseTests():
|
|
|
135
135
|
self.assertEqual(len(results), 1)
|
|
136
136
|
# Check some data
|
|
137
137
|
self.assertEqual(results[0]['id'], 3)
|
|
138
|
-
self.
|
|
138
|
+
self.assertIn(results[0]['name'], ('Admin', 'Mitchell Admin'))
|
|
139
139
|
self.assertEqual(results[0]['type'], 'contact')
|
|
140
140
|
self.assertGreater(len(results[0]['street']), 0)
|
|
141
141
|
|
|
@@ -322,7 +322,7 @@ class BaseTests():
|
|
|
322
322
|
self.assertEqual(len(results), 4)
|
|
323
323
|
# Check some data
|
|
324
324
|
self.assertEqual(results['id'], 3)
|
|
325
|
-
self.
|
|
325
|
+
self.assertIn(results['name'], ('Admin', 'Mitchell Admin'))
|
|
326
326
|
self.assertEqual(results['type'], 'contact')
|
|
327
327
|
self.assertGreater(len(results['street']), 0)
|
|
328
328
|
|
|
@@ -341,7 +341,7 @@ class BaseTests():
|
|
|
341
341
|
self.assertGreater(len(results), 0)
|
|
342
342
|
# Check some data
|
|
343
343
|
self.assertEqual(results[0]['id'], 3)
|
|
344
|
-
self.
|
|
344
|
+
self.assertIn(results[0]['name'], ('Admin', 'Mitchell Admin'))
|
|
345
345
|
self.assertEqual(results[0]['type'], 'contact')
|
|
346
346
|
self.assertGreater(len(results[0]['street']), 0)
|
|
347
347
|
|
|
@@ -674,7 +674,9 @@ class BaseTests():
|
|
|
674
674
|
body='This is an activity message',
|
|
675
675
|
author_id=results[0])
|
|
676
676
|
self.assertIsNotNone(entity_id)
|
|
677
|
-
self.assertGreater(entity_id
|
|
677
|
+
self.assertGreater(entity_id[0]
|
|
678
|
+
if isinstance(entity_id, list)
|
|
679
|
+
else entity_id, 0)
|
|
678
680
|
|
|
679
681
|
def test_34_post_message_comment(self
|
|
680
682
|
) -> None:
|
|
@@ -694,7 +696,9 @@ class BaseTests():
|
|
|
694
696
|
body='This is a comment message',
|
|
695
697
|
author_id=results[0])
|
|
696
698
|
self.assertIsNotNone(entity_id)
|
|
697
|
-
self.assertGreater(entity_id
|
|
699
|
+
self.assertGreater(entity_id[0]
|
|
700
|
+
if isinstance(entity_id, list)
|
|
701
|
+
else entity_id, 0)
|
|
698
702
|
|
|
699
703
|
def test_35_post_message_note(self
|
|
700
704
|
) -> None:
|
|
@@ -714,7 +718,9 @@ class BaseTests():
|
|
|
714
718
|
body='This is a note message',
|
|
715
719
|
author_id=results[0])
|
|
716
720
|
self.assertIsNotNone(entity_id)
|
|
717
|
-
self.assertGreater(entity_id
|
|
721
|
+
self.assertGreater(entity_id[0]
|
|
722
|
+
if isinstance(entity_id, list)
|
|
723
|
+
else entity_id, 0)
|
|
718
724
|
|
|
719
725
|
def test_36_delete(self
|
|
720
726
|
) -> None:
|
|
@@ -878,3 +884,16 @@ class TestCasesJsonRpc(BaseTests, unittest.TestCase):
|
|
|
878
884
|
model_name='res.partner',
|
|
879
885
|
implementation=Implementation.JSONRPC)
|
|
880
886
|
cls.model.authenticate()
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class TestCasesWebRpc(BaseTests, unittest.TestCase):
|
|
890
|
+
@classmethod
|
|
891
|
+
def setUpClass(cls
|
|
892
|
+
) -> None:
|
|
893
|
+
"""
|
|
894
|
+
Model object preparation
|
|
895
|
+
"""
|
|
896
|
+
cls.model = utility.get_model_from_demo(
|
|
897
|
+
model_name='res.partner',
|
|
898
|
+
implementation=Implementation.WEBRPC)
|
|
899
|
+
cls.model.authenticate()
|
|
@@ -163,3 +163,31 @@ class TestCasesJsonRpc(BaseTests, unittest.TestCase):
|
|
|
163
163
|
else:
|
|
164
164
|
# The model execute.python.code is not available
|
|
165
165
|
cls.script = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestCasesWebRpc(BaseTests, unittest.TestCase):
|
|
169
|
+
@classmethod
|
|
170
|
+
def setUpClass(cls
|
|
171
|
+
) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Python Code object preparation
|
|
174
|
+
"""
|
|
175
|
+
info = utility.get_authentication_from_demo()
|
|
176
|
+
if PythonCode.is_available(endpoint=info['host'],
|
|
177
|
+
database=info['database'],
|
|
178
|
+
username=info['user'],
|
|
179
|
+
password=info['password'],
|
|
180
|
+
language='en_US',
|
|
181
|
+
implementation=Implementation.WEBRPC):
|
|
182
|
+
# The model sql.excel.pdf is available
|
|
183
|
+
cls.script_name = uuid.uuid4().hex
|
|
184
|
+
cls.script = PythonCode(name=cls.script_name,
|
|
185
|
+
endpoint=info['host'],
|
|
186
|
+
database=info['database'],
|
|
187
|
+
username=info['user'],
|
|
188
|
+
password=info['password'],
|
|
189
|
+
language='en_US',
|
|
190
|
+
implementation=Implementation.WEBRPC)
|
|
191
|
+
else:
|
|
192
|
+
# The model execute.python.code is not available
|
|
193
|
+
cls.script = None
|
|
@@ -301,3 +301,32 @@ class TestCasesJsonRpc(BaseTests, unittest.TestCase):
|
|
|
301
301
|
else:
|
|
302
302
|
# The model sql.excel.pdf is not available
|
|
303
303
|
cls.query = None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class TestCasesWebRpc(BaseTests, unittest.TestCase):
|
|
307
|
+
@classmethod
|
|
308
|
+
def setUpClass(cls
|
|
309
|
+
) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Query object preparation
|
|
312
|
+
"""
|
|
313
|
+
info = utility.get_authentication_from_demo()
|
|
314
|
+
if SqlExcelQuery.is_available(endpoint=info['host'],
|
|
315
|
+
database=info['database'],
|
|
316
|
+
username=info['user'],
|
|
317
|
+
password=info['password'],
|
|
318
|
+
language='en_US',
|
|
319
|
+
implementation=Implementation.WEBRPC):
|
|
320
|
+
# The model sql.excel.pdf is available
|
|
321
|
+
cls.query_name = uuid.uuid4().hex
|
|
322
|
+
cls.query = SqlExcelQuery(name=cls.query_name,
|
|
323
|
+
category=f'{APP_NAME} {APP_VERSION}',
|
|
324
|
+
endpoint=info['host'],
|
|
325
|
+
database=info['database'],
|
|
326
|
+
username=info['user'],
|
|
327
|
+
password=info['password'],
|
|
328
|
+
language='en_US',
|
|
329
|
+
implementation=Implementation.WEBRPC)
|
|
330
|
+
else:
|
|
331
|
+
# The model sql.excel.pdf is not available
|
|
332
|
+
cls.query = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|