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.
Files changed (43) hide show
  1. {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
  2. mangono_addon_export_json-19.0.1.0.1.dist-info/RECORD +52 -0
  3. odoo/addons/export_json/README.rst +2 -2
  4. odoo/addons/export_json/__init__.py +4 -0
  5. odoo/addons/export_json/__manifest__.py +10 -3
  6. odoo/addons/export_json/controller/main.py +1 -1
  7. odoo/addons/export_json/demo/export_demo.xml +7 -0
  8. odoo/addons/export_json/demo/ir.exports.line.csv +16 -0
  9. odoo/addons/export_json/demo/resolver_demo.xml +12 -0
  10. odoo/addons/export_json/jsonifier/README.rst +284 -0
  11. odoo/addons/export_json/jsonifier/__init__.py +1 -0
  12. odoo/addons/export_json/jsonifier/__manifest__.old +27 -0
  13. odoo/addons/export_json/jsonifier/demo/export_demo.xml +7 -0
  14. odoo/addons/export_json/jsonifier/demo/ir.exports.line.csv +16 -0
  15. odoo/addons/export_json/jsonifier/demo/resolver_demo.xml +12 -0
  16. odoo/addons/export_json/jsonifier/exceptions.py +7 -0
  17. odoo/addons/export_json/jsonifier/i18n/ca.po +234 -0
  18. odoo/addons/export_json/jsonifier/i18n/es.po +266 -0
  19. odoo/addons/export_json/jsonifier/i18n/it.po +277 -0
  20. odoo/addons/export_json/jsonifier/i18n/zh_CN.po +260 -0
  21. odoo/addons/export_json/jsonifier/models/__init__.py +5 -0
  22. odoo/addons/export_json/jsonifier/models/ir_exports.py +124 -0
  23. odoo/addons/export_json/jsonifier/models/ir_exports_line.py +55 -0
  24. odoo/addons/export_json/jsonifier/models/ir_exports_resolver.py +58 -0
  25. odoo/addons/export_json/jsonifier/models/models.py +263 -0
  26. odoo/addons/export_json/jsonifier/models/utils.py +35 -0
  27. odoo/addons/export_json/jsonifier/pyproject.toml +3 -0
  28. odoo/addons/export_json/jsonifier/readme/CONTRIBUTORS.md +8 -0
  29. odoo/addons/export_json/jsonifier/readme/CREDITS.md +1 -0
  30. odoo/addons/export_json/jsonifier/readme/DESCRIPTION.md +163 -0
  31. odoo/addons/export_json/jsonifier/readme/USAGE.md +26 -0
  32. odoo/addons/export_json/jsonifier/security/ir.model.access.csv +2 -0
  33. odoo/addons/export_json/jsonifier/tests/__init__.py +3 -0
  34. odoo/addons/export_json/jsonifier/tests/test_get_parser.py +436 -0
  35. odoo/addons/export_json/jsonifier/tests/test_helpers.py +45 -0
  36. odoo/addons/export_json/jsonifier/tests/test_ir_exports_line.py +66 -0
  37. odoo/addons/export_json/jsonifier/views/ir_exports_resolver_view.xml +26 -0
  38. odoo/addons/export_json/jsonifier/views/ir_exports_view.xml +38 -0
  39. odoo/addons/export_json/models/file_json.py +1 -2
  40. odoo/addons/export_json/tests/test_export_json.py +14 -11
  41. mangono_addon_export_json-18.0.1.0.4.dist-info/RECORD +0 -20
  42. {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
  43. {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.FILE_TYPE_DICT["application/json"] = ("json", True, None)
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
- self.data_dict["fields"].append({"name": "title/name", "label": "Title"})
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
- title = (
183
- self.env["res.partner.title"]
183
+ category = (
184
+ self.env["res.partner.category"]
184
185
  .with_context(lang="en_US")
185
186
  .create(
186
187
  {
187
- "name": "Doctor",
188
+ "name": "Customer",
188
189
  }
189
190
  )
190
191
  )
191
- title.with_context(lang="fr_FR").name = "Docteur"
192
- self.partner.title = title
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
- "title": {
213
- "name": "title/name",
214
- "name_FR": "Docteur",
215
- "name_US": "Doctor",
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",