mangono-addon-export_json 18.0.1.0.4__py3-none-any.whl → 19.0.1.0.1__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.
- {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/METADATA +2 -3
- mangono_addon_export_json-19.0.1.0.1.dist-info/RECORD +52 -0
- odoo/addons/export_json/README.rst +2 -2
- odoo/addons/export_json/__init__.py +4 -0
- odoo/addons/export_json/__manifest__.py +10 -3
- odoo/addons/export_json/controller/main.py +1 -1
- odoo/addons/export_json/demo/export_demo.xml +7 -0
- odoo/addons/export_json/demo/ir.exports.line.csv +16 -0
- odoo/addons/export_json/demo/resolver_demo.xml +12 -0
- odoo/addons/export_json/jsonifier/README.rst +284 -0
- odoo/addons/export_json/jsonifier/__init__.py +1 -0
- odoo/addons/export_json/jsonifier/__manifest__.old +27 -0
- odoo/addons/export_json/jsonifier/demo/export_demo.xml +7 -0
- odoo/addons/export_json/jsonifier/demo/ir.exports.line.csv +16 -0
- odoo/addons/export_json/jsonifier/demo/resolver_demo.xml +12 -0
- odoo/addons/export_json/jsonifier/exceptions.py +7 -0
- odoo/addons/export_json/jsonifier/i18n/ca.po +234 -0
- odoo/addons/export_json/jsonifier/i18n/es.po +266 -0
- odoo/addons/export_json/jsonifier/i18n/it.po +277 -0
- odoo/addons/export_json/jsonifier/i18n/zh_CN.po +260 -0
- odoo/addons/export_json/jsonifier/models/__init__.py +5 -0
- odoo/addons/export_json/jsonifier/models/ir_exports.py +124 -0
- odoo/addons/export_json/jsonifier/models/ir_exports_line.py +55 -0
- odoo/addons/export_json/jsonifier/models/ir_exports_resolver.py +58 -0
- odoo/addons/export_json/jsonifier/models/models.py +263 -0
- odoo/addons/export_json/jsonifier/models/utils.py +35 -0
- odoo/addons/export_json/jsonifier/pyproject.toml +3 -0
- odoo/addons/export_json/jsonifier/readme/CONTRIBUTORS.md +8 -0
- odoo/addons/export_json/jsonifier/readme/CREDITS.md +1 -0
- odoo/addons/export_json/jsonifier/readme/DESCRIPTION.md +163 -0
- odoo/addons/export_json/jsonifier/readme/USAGE.md +26 -0
- odoo/addons/export_json/jsonifier/security/ir.model.access.csv +2 -0
- odoo/addons/export_json/jsonifier/tests/__init__.py +3 -0
- odoo/addons/export_json/jsonifier/tests/test_get_parser.py +436 -0
- odoo/addons/export_json/jsonifier/tests/test_helpers.py +45 -0
- odoo/addons/export_json/jsonifier/tests/test_ir_exports_line.py +66 -0
- odoo/addons/export_json/jsonifier/views/ir_exports_resolver_view.xml +26 -0
- odoo/addons/export_json/jsonifier/views/ir_exports_view.xml +38 -0
- odoo/addons/export_json/models/file_json.py +1 -2
- odoo/addons/export_json/tests/test_export_json.py +14 -11
- mangono_addon_export_json-18.0.1.0.4.dist-info/RECORD +0 -20
- {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/WHEEL +0 -0
- {mangono_addon_export_json-18.0.1.0.4.dist-info → mangono_addon_export_json-19.0.1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# Copyright 2017 ACSONE SA/NV
|
|
2
|
+
# Copyright 2022 Camptocamp SA (http://www.camptocamp.com)
|
|
3
|
+
# Simone Orsi <simahawk@gmail.com>
|
|
4
|
+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from odoo import tools
|
|
8
|
+
from odoo.exceptions import UserError
|
|
9
|
+
from odoo.tests.common import TransactionCase
|
|
10
|
+
|
|
11
|
+
from ..models.utils import convert_simple_to_full_parser
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def jsonify_custom(self, field_name):
|
|
15
|
+
return "yeah!"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestParser(TransactionCase):
|
|
19
|
+
@classmethod
|
|
20
|
+
def setUpClass(cls):
|
|
21
|
+
super().setUpClass()
|
|
22
|
+
# disable tracking test suite wise
|
|
23
|
+
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
|
24
|
+
cls.env.user.tz = "Europe/Brussels"
|
|
25
|
+
cls.partner = cls.env["res.partner"].create(
|
|
26
|
+
{
|
|
27
|
+
"name": "Akretion",
|
|
28
|
+
"country_id": cls.env.ref("base.fr").id,
|
|
29
|
+
"lang": "en_US", # default
|
|
30
|
+
"category_id": [(0, 0, {"name": "Inovator"})],
|
|
31
|
+
"child_ids": [
|
|
32
|
+
(
|
|
33
|
+
0,
|
|
34
|
+
0,
|
|
35
|
+
{
|
|
36
|
+
"name": "Sebatien Beau",
|
|
37
|
+
"country_id": cls.env.ref("base.fr").id,
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
Langs = cls.env["res.lang"].with_context(active_test=False)
|
|
44
|
+
cls.lang = Langs.search([("code", "=", "fr_FR")])
|
|
45
|
+
cls.lang.active = True
|
|
46
|
+
category = cls.env["res.partner.category"].create({"name": "name"})
|
|
47
|
+
cls.translated_target = f"name_{cls.lang.code}"
|
|
48
|
+
category.with_context(lang=cls.lang.code).write({"name": cls.translated_target})
|
|
49
|
+
cls.global_resolver = cls.env["ir.exports.resolver"].create(
|
|
50
|
+
{"python_code": "value['X'] = 'X'; result = value", "type": "global"}
|
|
51
|
+
)
|
|
52
|
+
cls.resolver = cls.env["ir.exports.resolver"].create(
|
|
53
|
+
{"python_code": "result = value + '_pidgin'", "type": "field"}
|
|
54
|
+
)
|
|
55
|
+
cls.category_export = cls.env["ir.exports"].create(
|
|
56
|
+
{
|
|
57
|
+
"global_resolver_id": cls.global_resolver.id,
|
|
58
|
+
"language_agnostic": True,
|
|
59
|
+
"export_fields": [
|
|
60
|
+
(0, 0, {"name": "name"}),
|
|
61
|
+
(
|
|
62
|
+
0,
|
|
63
|
+
0,
|
|
64
|
+
{
|
|
65
|
+
"name": "name",
|
|
66
|
+
"target": f"name:{cls.translated_target}",
|
|
67
|
+
"lang_id": cls.lang.id,
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
0,
|
|
72
|
+
0,
|
|
73
|
+
{
|
|
74
|
+
"name": "name",
|
|
75
|
+
"target": "name:name_resolved",
|
|
76
|
+
"resolver_id": cls.resolver.id,
|
|
77
|
+
},
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
cls.category = category.with_context(lang=None)
|
|
83
|
+
cls.category_lang = category.with_context(lang=cls.lang.code)
|
|
84
|
+
|
|
85
|
+
def test_getting_parser(self):
|
|
86
|
+
expected_parser = [
|
|
87
|
+
"name",
|
|
88
|
+
"active",
|
|
89
|
+
"partner_latitude",
|
|
90
|
+
"color",
|
|
91
|
+
("category_id", ["name"]),
|
|
92
|
+
("country_id", ["name", "code"]),
|
|
93
|
+
(
|
|
94
|
+
"child_ids",
|
|
95
|
+
[
|
|
96
|
+
"name",
|
|
97
|
+
"id",
|
|
98
|
+
"email",
|
|
99
|
+
("country_id", ["name", "code"]),
|
|
100
|
+
("child_ids", ["name"]),
|
|
101
|
+
],
|
|
102
|
+
),
|
|
103
|
+
"lang",
|
|
104
|
+
"comment",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
exporter = self.env.ref("jsonifier.ir_exp_partner")
|
|
108
|
+
parser = exporter.get_json_parser()
|
|
109
|
+
expected_full_parser = convert_simple_to_full_parser(expected_parser)
|
|
110
|
+
self.assertEqual(parser, expected_full_parser)
|
|
111
|
+
|
|
112
|
+
# modify an ir.exports_line to put a target for a field
|
|
113
|
+
self.env.ref("jsonifier.category_id_name").write({"target": "category_id:category/name"})
|
|
114
|
+
expected_parser[4] = ("category_id:category", ["name"])
|
|
115
|
+
parser = exporter.get_json_parser()
|
|
116
|
+
expected_full_parser = convert_simple_to_full_parser(expected_parser)
|
|
117
|
+
self.assertEqual(parser, expected_full_parser)
|
|
118
|
+
|
|
119
|
+
def test_json_export(self):
|
|
120
|
+
# will allow to view large dict diff in case of regression
|
|
121
|
+
self.maxDiff = None
|
|
122
|
+
# Enforces TZ to validate the serialization result of a Datetime
|
|
123
|
+
parser = [
|
|
124
|
+
"lang",
|
|
125
|
+
"comment",
|
|
126
|
+
"partner_latitude",
|
|
127
|
+
"name",
|
|
128
|
+
"color",
|
|
129
|
+
(
|
|
130
|
+
"child_ids:children",
|
|
131
|
+
[
|
|
132
|
+
("child_ids:children", ["name"]),
|
|
133
|
+
"email",
|
|
134
|
+
("country_id:country", ["code", "name"]),
|
|
135
|
+
"name",
|
|
136
|
+
"id",
|
|
137
|
+
],
|
|
138
|
+
),
|
|
139
|
+
("country_id:country", ["code", "name"]),
|
|
140
|
+
"active",
|
|
141
|
+
("category_id", ["name"]),
|
|
142
|
+
"create_date",
|
|
143
|
+
]
|
|
144
|
+
# put our own create date to ease tests
|
|
145
|
+
self.env.cr.execute(
|
|
146
|
+
"update res_partner set create_date=%s where id=%s",
|
|
147
|
+
("2019-10-31 14:39:49", self.partner.id),
|
|
148
|
+
)
|
|
149
|
+
expected_json = {
|
|
150
|
+
"lang": "en_US",
|
|
151
|
+
"comment": None,
|
|
152
|
+
"partner_latitude": 0.0,
|
|
153
|
+
"name": "Akretion",
|
|
154
|
+
"color": 0,
|
|
155
|
+
"country": {"code": "FR", "name": "France"},
|
|
156
|
+
"active": True,
|
|
157
|
+
"category_id": [{"name": "Inovator"}],
|
|
158
|
+
"children": [
|
|
159
|
+
{
|
|
160
|
+
"id": self.partner.child_ids.id,
|
|
161
|
+
"country": {"code": "FR", "name": "France"},
|
|
162
|
+
"children": [],
|
|
163
|
+
"name": "Sebatien Beau",
|
|
164
|
+
"email": None,
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"create_date": "2019-10-31T14:39:49",
|
|
168
|
+
}
|
|
169
|
+
expected_json_with_fieldname = {
|
|
170
|
+
"_fieldname_lang": "Language",
|
|
171
|
+
"lang": "en_US",
|
|
172
|
+
"_fieldname_comment": "Notes",
|
|
173
|
+
"comment": None,
|
|
174
|
+
"partner_latitude": 0.0,
|
|
175
|
+
"_fieldname_name": "Name",
|
|
176
|
+
"name": "Akretion",
|
|
177
|
+
"_fieldname_color": "Color Index",
|
|
178
|
+
"color": 0,
|
|
179
|
+
"_fieldname_children": "Contact",
|
|
180
|
+
"children": [
|
|
181
|
+
{
|
|
182
|
+
"_fieldname_children": "Contact",
|
|
183
|
+
"children": [],
|
|
184
|
+
"_fieldname_email": "Email",
|
|
185
|
+
"email": None,
|
|
186
|
+
"_fieldname_country": "Country",
|
|
187
|
+
"country": {
|
|
188
|
+
"_fieldname_code": "Country Code",
|
|
189
|
+
"code": "FR",
|
|
190
|
+
"_fieldname_name": "Country Name",
|
|
191
|
+
"name": "France",
|
|
192
|
+
},
|
|
193
|
+
"_fieldname_name": "Name",
|
|
194
|
+
"name": "Sebatien Beau",
|
|
195
|
+
"_fieldname_id": "ID",
|
|
196
|
+
"id": self.partner.child_ids.id,
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"_fieldname_country": "Country",
|
|
200
|
+
"country": {
|
|
201
|
+
"_fieldname_code": "Country Code",
|
|
202
|
+
"code": "FR",
|
|
203
|
+
"_fieldname_name": "Country Name",
|
|
204
|
+
"name": "France",
|
|
205
|
+
},
|
|
206
|
+
"_fieldname_active": "Active",
|
|
207
|
+
"active": True,
|
|
208
|
+
"_fieldname_category_id": "Tags",
|
|
209
|
+
"category_id": [{"_fieldname_name": "Name", "name": "Inovator"}],
|
|
210
|
+
"_fieldname_create_date": "Created on",
|
|
211
|
+
"_fieldname_partner_latitude": "Geo Latitude",
|
|
212
|
+
"create_date": "2019-10-31T14:39:49",
|
|
213
|
+
}
|
|
214
|
+
expected_json_with_fieldname = {
|
|
215
|
+
"_fieldname_lang": "Language",
|
|
216
|
+
"lang": "en_US",
|
|
217
|
+
"_fieldname_comment": "Notes",
|
|
218
|
+
"comment": None,
|
|
219
|
+
"_fieldname_partner_latitude": "Geo Latitude",
|
|
220
|
+
"_fieldname_name": "Name",
|
|
221
|
+
"name": "Akretion",
|
|
222
|
+
"_fieldname_color": "Color Index",
|
|
223
|
+
"color": 0,
|
|
224
|
+
"_fieldname_children": "Contact",
|
|
225
|
+
"children": [
|
|
226
|
+
{
|
|
227
|
+
"_fieldname_children": "Contact",
|
|
228
|
+
"children": [],
|
|
229
|
+
"_fieldname_email": "Email",
|
|
230
|
+
"email": None,
|
|
231
|
+
"_fieldname_country": "Country",
|
|
232
|
+
"country": {
|
|
233
|
+
"_fieldname_code": "Country Code",
|
|
234
|
+
"code": "FR",
|
|
235
|
+
"_fieldname_name": "Country Name",
|
|
236
|
+
"name": "France",
|
|
237
|
+
},
|
|
238
|
+
"_fieldname_name": "Name",
|
|
239
|
+
"name": "Sebatien Beau",
|
|
240
|
+
"_fieldname_id": "ID",
|
|
241
|
+
"id": self.partner.child_ids.id,
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"_fieldname_country": "Country",
|
|
245
|
+
"country": {
|
|
246
|
+
"_fieldname_code": "Country Code",
|
|
247
|
+
"code": "FR",
|
|
248
|
+
"_fieldname_name": "Country Name",
|
|
249
|
+
"name": "France",
|
|
250
|
+
},
|
|
251
|
+
"_fieldname_active": "Active",
|
|
252
|
+
"active": True,
|
|
253
|
+
"_fieldname_category_id": "Tags",
|
|
254
|
+
"category_id": [{"_fieldname_name": "Name", "name": "Inovator"}],
|
|
255
|
+
"_fieldname_create_date": "Created on",
|
|
256
|
+
"create_date": "2019-10-31T14:39:49",
|
|
257
|
+
"partner_latitude": 0.0,
|
|
258
|
+
}
|
|
259
|
+
json_partner = self.partner.jsonify(parser)
|
|
260
|
+
self.assertDictEqual(json_partner[0], expected_json)
|
|
261
|
+
json_partner_with_fieldname = self.partner.jsonify(parser=parser, with_fieldname=True)
|
|
262
|
+
self.assertDictEqual(json_partner_with_fieldname[0], expected_json_with_fieldname)
|
|
263
|
+
# Check that only boolean fields have boolean values into json
|
|
264
|
+
# By default if a field is not set into Odoo, the value is always False
|
|
265
|
+
# This value is not the expected one into the json
|
|
266
|
+
self.partner.write({"child_ids": [(6, 0, [])], "active": False, "lang": False})
|
|
267
|
+
json_partner = self.partner.jsonify(parser)
|
|
268
|
+
expected_json["active"] = False
|
|
269
|
+
expected_json["lang"] = None
|
|
270
|
+
expected_json["children"] = []
|
|
271
|
+
self.assertDictEqual(json_partner[0], expected_json)
|
|
272
|
+
|
|
273
|
+
def test_one(self):
|
|
274
|
+
parser = [
|
|
275
|
+
"name",
|
|
276
|
+
]
|
|
277
|
+
expected_json = {
|
|
278
|
+
"name": "Akretion",
|
|
279
|
+
}
|
|
280
|
+
json_partner = self.partner.jsonify(parser, one=True)
|
|
281
|
+
self.assertDictEqual(json_partner, expected_json)
|
|
282
|
+
# cannot call on multiple records
|
|
283
|
+
with self.assertRaises(ValueError) as err:
|
|
284
|
+
self.env["res.partner"].search([]).jsonify(parser, one=True)
|
|
285
|
+
self.assertIn("Expected singleton", str(err.exception))
|
|
286
|
+
|
|
287
|
+
def test_json_export_callable_parser(self):
|
|
288
|
+
self.partner.__class__.jsonify_custom = jsonify_custom
|
|
289
|
+
parser = [
|
|
290
|
+
# callable subparser
|
|
291
|
+
("name", lambda rec, fname: rec[fname] + " rocks!"),
|
|
292
|
+
("name:custom", "jsonify_custom"),
|
|
293
|
+
("unknown_field", lambda rec, fname: "yeah again!"),
|
|
294
|
+
]
|
|
295
|
+
expected_json = {
|
|
296
|
+
"name": "Akretion rocks!",
|
|
297
|
+
"custom": "yeah!",
|
|
298
|
+
"unknown_field": "yeah again!",
|
|
299
|
+
}
|
|
300
|
+
json_partner = self.partner.jsonify(parser)
|
|
301
|
+
self.assertDictEqual(json_partner[0], expected_json)
|
|
302
|
+
del self.partner.__class__.jsonify_custom
|
|
303
|
+
|
|
304
|
+
def test_full_parser(self):
|
|
305
|
+
parser = self.category_export.get_json_parser()
|
|
306
|
+
json = self.category.jsonify(parser)[0]
|
|
307
|
+
json_fr = self.category_lang.jsonify(parser)[0]
|
|
308
|
+
|
|
309
|
+
self.assertEqual(json, json_fr) # starting from different languages should not change anything
|
|
310
|
+
self.assertEqual(json[self.translated_target], self.translated_target)
|
|
311
|
+
self.assertEqual(json["name_resolved"], "name_pidgin") # field resolver
|
|
312
|
+
self.assertEqual(json["X"], "X") # added by global resolver
|
|
313
|
+
|
|
314
|
+
def test_full_parser_resolver_json_key_override(self):
|
|
315
|
+
self.resolver.write({"python_code": """result = {"_json_key": "foo", "_value": record.id}"""})
|
|
316
|
+
parser = self.category_export.get_json_parser()
|
|
317
|
+
json = self.category.jsonify(parser)[0]
|
|
318
|
+
self.assertNotIn("name_resolved", json)
|
|
319
|
+
self.assertEqual(json["foo"], self.category.id) # field resolver
|
|
320
|
+
self.assertEqual(json["X"], "X") # added by global resolver
|
|
321
|
+
|
|
322
|
+
def test_simple_parser_translations(self):
|
|
323
|
+
"""The simple parser result should depend on the context language."""
|
|
324
|
+
parser = ["name"]
|
|
325
|
+
json = self.category.jsonify(parser)[0]
|
|
326
|
+
json_fr = self.category_lang.jsonify(parser)[0]
|
|
327
|
+
|
|
328
|
+
self.assertEqual(json["name"], "name")
|
|
329
|
+
self.assertEqual(json_fr["name"], self.translated_target)
|
|
330
|
+
|
|
331
|
+
def test_simple_star_target_and_field_resolver(self):
|
|
332
|
+
"""The simple parser result should depend on the context language."""
|
|
333
|
+
code = (
|
|
334
|
+
"is_number = field_type in ('integer', 'float');"
|
|
335
|
+
"ftype = 'NUMBER' if is_number else 'TEXT';"
|
|
336
|
+
"value = value if is_number else str(value);"
|
|
337
|
+
"result = {'Key': name, 'Value': value, 'Type': ftype, 'IsPublic': True}"
|
|
338
|
+
)
|
|
339
|
+
resolver = self.env["ir.exports.resolver"].create({"python_code": code})
|
|
340
|
+
lang_parser = [
|
|
341
|
+
{"target": "customTags=list", "name": "name", "resolver": resolver},
|
|
342
|
+
{"target": "customTags=list", "name": "id", "resolver": resolver},
|
|
343
|
+
]
|
|
344
|
+
parser = {"language_agnostic": True, "langs": {False: lang_parser}}
|
|
345
|
+
expected_json = {
|
|
346
|
+
"customTags": [
|
|
347
|
+
{"Value": "name", "Key": "name", "Type": "TEXT", "IsPublic": True},
|
|
348
|
+
{
|
|
349
|
+
"Value": self.category.id,
|
|
350
|
+
"Key": "id",
|
|
351
|
+
"Type": "NUMBER",
|
|
352
|
+
"IsPublic": True,
|
|
353
|
+
},
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
json = self.category.jsonify(parser)[0]
|
|
358
|
+
self.assertEqual(json, expected_json)
|
|
359
|
+
|
|
360
|
+
def test_simple_export_with_function(self):
|
|
361
|
+
self.category.__class__.jsonify_custom = jsonify_custom
|
|
362
|
+
export = self.env["ir.exports"].create(
|
|
363
|
+
{
|
|
364
|
+
"export_fields": [
|
|
365
|
+
(0, 0, {"name": "name", "instance_method_name": "jsonify_custom"}),
|
|
366
|
+
],
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
json = self.category.jsonify(export.get_json_parser())[0]
|
|
371
|
+
self.assertEqual(json, {"name": "yeah!"})
|
|
372
|
+
|
|
373
|
+
def test_export_relational_display_names(self):
|
|
374
|
+
"""If we export a relational, we get its display_name in the json."""
|
|
375
|
+
parser = [
|
|
376
|
+
"state_id",
|
|
377
|
+
"country_id",
|
|
378
|
+
"category_id",
|
|
379
|
+
"user_ids",
|
|
380
|
+
]
|
|
381
|
+
expected_json = {
|
|
382
|
+
"state_id": None,
|
|
383
|
+
"country_id": "France",
|
|
384
|
+
"category_id": ["Inovator"],
|
|
385
|
+
"user_ids": [],
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
json_partner = self.partner.jsonify(parser, one=True)
|
|
389
|
+
|
|
390
|
+
self.assertDictEqual(json_partner, expected_json)
|
|
391
|
+
|
|
392
|
+
def test_export_reference_display_names(self):
|
|
393
|
+
"""Reference work the same as relational"""
|
|
394
|
+
menu = self.env.ref("base.menu_action_res_users")
|
|
395
|
+
|
|
396
|
+
json_menu = menu.jsonify(["action"], one=True)
|
|
397
|
+
|
|
398
|
+
self.assertDictEqual(json_menu, {"action": "Users"})
|
|
399
|
+
|
|
400
|
+
def test_bad_parsers_strict(self):
|
|
401
|
+
rec = self.category.with_context(jsonify_record_strict=True)
|
|
402
|
+
bad_field_name = ["Name"]
|
|
403
|
+
with self.assertRaises(KeyError):
|
|
404
|
+
rec.jsonify(bad_field_name, one=True)
|
|
405
|
+
|
|
406
|
+
bad_function_name = {"fields": [{"name": "name", "function": "notafunction"}]}
|
|
407
|
+
with self.assertRaises(UserError):
|
|
408
|
+
rec.jsonify(bad_function_name, one=True)
|
|
409
|
+
|
|
410
|
+
bad_subparser = {"fields": [({"name": "name"}, [{"name": "subparser_name"}])]}
|
|
411
|
+
with self.assertRaises(UserError):
|
|
412
|
+
rec.jsonify(bad_subparser, one=True)
|
|
413
|
+
|
|
414
|
+
def test_bad_parsers_fail_gracefully(self):
|
|
415
|
+
rec = self.category
|
|
416
|
+
|
|
417
|
+
# logging is disabled when testing as it makes too much noise
|
|
418
|
+
tools.config["test_enable"] = False
|
|
419
|
+
|
|
420
|
+
logger_name = "odoo.addons.jsonifier.models.models"
|
|
421
|
+
bad_field_name = ["Name"]
|
|
422
|
+
with self.assertLogs(logger=logger_name, level="WARNING") as capt:
|
|
423
|
+
rec.jsonify(bad_field_name, one=True)
|
|
424
|
+
self.assertIn("res.partner.category.Name not availabl", capt.output[0])
|
|
425
|
+
|
|
426
|
+
bad_function_name = {"fields": [{"name": "name", "function": "notafunction"}]}
|
|
427
|
+
with self.assertLogs(logger=logger_name, level="WARNING") as capt:
|
|
428
|
+
rec.jsonify(bad_function_name, one=True)
|
|
429
|
+
self.assertIn("res.partner.category.notafunction not available", capt.output[0])
|
|
430
|
+
|
|
431
|
+
bad_subparser = {"fields": [({"name": "name"}, [{"name": "subparser_name"}])]}
|
|
432
|
+
with self.assertLogs(logger=logger_name, level="WARNING") as capt:
|
|
433
|
+
rec.jsonify(bad_subparser, one=True)
|
|
434
|
+
self.assertIn("res.partner.category.name not relational", capt.output[0])
|
|
435
|
+
|
|
436
|
+
tools.config["test_enable"] = True
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright 2021 Camptocamp SA (https://www.camptocamp.com).
|
|
2
|
+
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
|
3
|
+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
4
|
+
|
|
5
|
+
from odoo.tests.common import TransactionCase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestJsonifyHelpers(TransactionCase):
|
|
9
|
+
@classmethod
|
|
10
|
+
def setUpClass(cls):
|
|
11
|
+
super().setUpClass()
|
|
12
|
+
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
|
13
|
+
cls.partner = cls.env["res.partner"].create(
|
|
14
|
+
{
|
|
15
|
+
"name": "My Partner",
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
cls.children = cls.env["res.partner"].create(
|
|
19
|
+
[
|
|
20
|
+
{"parent_id": cls.partner.id, "name": "Child 1"},
|
|
21
|
+
{"parent_id": cls.partner.id, "name": "Child 2"},
|
|
22
|
+
]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def test_helper_m2o_to_id(self):
|
|
26
|
+
child = self.children[0]
|
|
27
|
+
self.assertEqual(
|
|
28
|
+
child._jsonify_m2o_to_id("parent_id"),
|
|
29
|
+
child.parent_id.id,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def test_helper_m2m_to_ids(self):
|
|
33
|
+
self.assertEqual(
|
|
34
|
+
self.partner._jsonify_x2m_to_ids("child_ids"),
|
|
35
|
+
self.partner.child_ids.ids,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def test_helper_format_duration(self):
|
|
39
|
+
# partner_latitude is not intended for this, but it's a float field in core
|
|
40
|
+
# any float field does the trick here
|
|
41
|
+
self.partner.partner_latitude = 15.5
|
|
42
|
+
self.assertEqual(
|
|
43
|
+
self.partner._jsonify_format_duration("partner_latitude"),
|
|
44
|
+
"15:30",
|
|
45
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright 2017 ACSONE SA/NV
|
|
2
|
+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
|
3
|
+
|
|
4
|
+
from odoo.exceptions import ValidationError
|
|
5
|
+
from odoo.tests.common import TransactionCase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestIrExportsLine(TransactionCase):
|
|
9
|
+
@classmethod
|
|
10
|
+
def setUpClass(cls):
|
|
11
|
+
super().setUpClass()
|
|
12
|
+
cls.ir_export = cls.env.ref("jsonifier.ir_exp_partner")
|
|
13
|
+
|
|
14
|
+
def test_target_constrains(self):
|
|
15
|
+
ir_export_lines_model = self.env["ir.exports.line"]
|
|
16
|
+
with self.assertRaises(ValidationError):
|
|
17
|
+
# The field into the name must be also into the target
|
|
18
|
+
ir_export_lines_model.create(
|
|
19
|
+
{
|
|
20
|
+
"export_id": self.ir_export.id,
|
|
21
|
+
"name": "name",
|
|
22
|
+
"target": "toto:my_target",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
with self.assertRaises(ValidationError):
|
|
26
|
+
# The hierarchy into the target must be the same as the one into
|
|
27
|
+
# the name
|
|
28
|
+
ir_export_lines_model.create(
|
|
29
|
+
{
|
|
30
|
+
"export_id": self.ir_export.id,
|
|
31
|
+
"name": "child_ids/child_ids/name",
|
|
32
|
+
"target": "child_ids:children/name",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
with self.assertRaises(ValidationError):
|
|
36
|
+
# The hierarchy into the target must be the same as the one into
|
|
37
|
+
# the name and must contains the same fields as into the name
|
|
38
|
+
ir_export_lines_model.create(
|
|
39
|
+
{
|
|
40
|
+
"export_id": self.ir_export.id,
|
|
41
|
+
"name": "child_ids/child_ids/name",
|
|
42
|
+
"target": "child_ids:children/category_id:category/name",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
line = ir_export_lines_model.create(
|
|
46
|
+
{
|
|
47
|
+
"export_id": self.ir_export.id,
|
|
48
|
+
"name": "child_ids/child_ids/name",
|
|
49
|
+
"target": "child_ids:children/child_ids:children/name",
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
self.assertTrue(line)
|
|
53
|
+
|
|
54
|
+
def test_resolver_function_constrains(self):
|
|
55
|
+
resolver = self.env["ir.exports.resolver"].create({"python_code": "result = value", "type": "field"})
|
|
56
|
+
ir_export_lines_model = self.env["ir.exports.line"]
|
|
57
|
+
with self.assertRaises(ValidationError):
|
|
58
|
+
# the callable should be an existing model function, but it's not checked
|
|
59
|
+
ir_export_lines_model.create(
|
|
60
|
+
{
|
|
61
|
+
"export_id": self.ir_export.id,
|
|
62
|
+
"name": "name",
|
|
63
|
+
"resolver_id": resolver.id,
|
|
64
|
+
"instance_method_name": "function_name",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<odoo>
|
|
3
|
+
<record model="ir.ui.view" id="view_ir_exports_resolver">
|
|
4
|
+
<field name="model">ir.exports.resolver</field>
|
|
5
|
+
<field name="priority">50</field>
|
|
6
|
+
<field name="arch" type="xml">
|
|
7
|
+
<form>
|
|
8
|
+
<group>
|
|
9
|
+
<field name="name" />
|
|
10
|
+
<field name="type" />
|
|
11
|
+
<field name="python_code" />
|
|
12
|
+
</group>
|
|
13
|
+
</form>
|
|
14
|
+
</field>
|
|
15
|
+
</record>
|
|
16
|
+
<record id="act_ui_exports_resolver_view" model="ir.actions.act_window">
|
|
17
|
+
<field name="name">Custom Export Resolvers</field>
|
|
18
|
+
<field name="res_model">ir.exports.resolver</field>
|
|
19
|
+
<field name="view_mode">list,form</field>
|
|
20
|
+
</record>
|
|
21
|
+
<menuitem
|
|
22
|
+
id="ui_exports_resolvers"
|
|
23
|
+
action="act_ui_exports_resolver_view"
|
|
24
|
+
parent="base.next_id_2"
|
|
25
|
+
/>
|
|
26
|
+
</odoo>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<odoo>
|
|
3
|
+
<record model="ir.ui.view" id="view_ir_exports">
|
|
4
|
+
<field name="model">ir.exports</field>
|
|
5
|
+
<field name="priority">50</field>
|
|
6
|
+
<field name="arch" type="xml">
|
|
7
|
+
<form>
|
|
8
|
+
<sheet>
|
|
9
|
+
<group name="se" string="Configuration">
|
|
10
|
+
<group colspan="4" col="4" name="se-main">
|
|
11
|
+
<field name="name" />
|
|
12
|
+
<field name="resource" />
|
|
13
|
+
<field name="language_agnostic" />
|
|
14
|
+
<field name="global_resolver_id" />
|
|
15
|
+
</group>
|
|
16
|
+
</group>
|
|
17
|
+
<group name="index" string="Index">
|
|
18
|
+
<field name="export_fields" nolabel="1" colspan="2">
|
|
19
|
+
<list editable="bottom">
|
|
20
|
+
<field name="name" />
|
|
21
|
+
<field name="target" />
|
|
22
|
+
<field name="lang_id" />
|
|
23
|
+
<field name="resolver_id" />
|
|
24
|
+
<field name="instance_method_name" />
|
|
25
|
+
</list>
|
|
26
|
+
</field>
|
|
27
|
+
</group>
|
|
28
|
+
</sheet>
|
|
29
|
+
</form>
|
|
30
|
+
</field>
|
|
31
|
+
</record>
|
|
32
|
+
<record id="act_ui_exports_view" model="ir.actions.act_window">
|
|
33
|
+
<field name="name">Export Fields</field>
|
|
34
|
+
<field name="res_model">ir.exports</field>
|
|
35
|
+
<field name="view_mode">list,form</field>
|
|
36
|
+
</record>
|
|
37
|
+
<menuitem id="ui_exports" action="act_ui_exports_view" parent="base.next_id_2" />
|
|
38
|
+
</odoo>
|
|
@@ -4,8 +4,7 @@ from odoo import models
|
|
|
4
4
|
|
|
5
5
|
from odoo.addons.base_import import models as model_import
|
|
6
6
|
|
|
7
|
-
model_import.base_import.
|
|
8
|
-
model_import.base_import.EXTENSIONS[".json"] = True
|
|
7
|
+
model_import.base_import.MIMETYPE_TO_READER["application/json"] = "json"
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class BaseImportJSON(models.TransientModel):
|
|
@@ -173,23 +173,24 @@ class TestExportJson(common.HttpCase):
|
|
|
173
173
|
def test_perform_json_export_with_lang(self):
|
|
174
174
|
self.env["res.lang"]._activate_lang("fr_FR")
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
# category_id is a M2M, not a M2O
|
|
177
|
+
self.data_dict["fields"].append({"name": "category_id/name", "label": "Tags"})
|
|
177
178
|
self.data_dict["fields"].append({"name": "country_id/name", "label": "Country"})
|
|
178
179
|
fields = [field["name"] for field in self.data_dict["fields"]]
|
|
179
180
|
model = self.env[self.data_dict["model"]]
|
|
180
181
|
|
|
181
182
|
# Ajouts de traductions
|
|
182
|
-
|
|
183
|
-
self.env["res.partner.
|
|
183
|
+
category = (
|
|
184
|
+
self.env["res.partner.category"]
|
|
184
185
|
.with_context(lang="en_US")
|
|
185
186
|
.create(
|
|
186
187
|
{
|
|
187
|
-
"name": "
|
|
188
|
+
"name": "Customer",
|
|
188
189
|
}
|
|
189
190
|
)
|
|
190
191
|
)
|
|
191
|
-
|
|
192
|
-
self.partner.
|
|
192
|
+
category.with_context(lang="fr_FR").name = "Client"
|
|
193
|
+
self.partner.category_id = category
|
|
193
194
|
|
|
194
195
|
self.env.ref("base.es").with_context(lang="fr_FR").name = "Espagne"
|
|
195
196
|
self.partner.country_id = self.env.ref("base.es")
|
|
@@ -209,11 +210,13 @@ class TestExportJson(common.HttpCase):
|
|
|
209
210
|
"name": "TEST",
|
|
210
211
|
"email": "test@test.fr",
|
|
211
212
|
"type": "contact",
|
|
212
|
-
"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
"category_id": [
|
|
214
|
+
{
|
|
215
|
+
"name": "category_id/name",
|
|
216
|
+
"name_FR": "Client",
|
|
217
|
+
"name_US": "Customer",
|
|
218
|
+
}
|
|
219
|
+
],
|
|
217
220
|
"country_id": {
|
|
218
221
|
"name": "country_id/name",
|
|
219
222
|
"name_FR": "Espagne",
|