mangono-addon-export_json 18.0.1.0.4__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.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: mangono-addon-export_json
3
+ Version: 18.0.1.0.4
4
+ Project-URL: Homepage, https://mangono.fr/
5
+ Author-Email: Mangono <opensource+odoo@mangono.fr>
6
+ License-Expression: AGPL-3.0
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Framework :: Odoo
9
+ Classifier: Framework :: Odoo :: 18.0
10
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
11
+ Classifier: Programming Language :: Python
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+
15
+ JSON Export system for Odoo Community edition.
@@ -0,0 +1,20 @@
1
+ odoo/addons/export_json/README.rst,sha256=gMsnJJoa-OPAqRu06ujSsHo1fZleF7ghmvGcFhtEI40,1046
2
+ odoo/addons/export_json/README_PYPI.md,sha256=EPAGNA3CPAnOmE8NYvVP6WpPt1QAr2LMMjl4Aj2zanw,47
3
+ odoo/addons/export_json/__init__.py,sha256=YnB_7kwpKh3zzUDSXSh5Rar3qTovcb6woTRlGqwmfKM,46
4
+ odoo/addons/export_json/__manifest__.py,sha256=y6Rp9seKTITY_xzH-84CiipYMKRx3JKckwvX3TQ6mWM,524
5
+ odoo/addons/export_json/controller/__init__.py,sha256=4KFqEP2QHFbPN66eQJMdGsmNz2v7ywWv_FR1pW_kkLk,19
6
+ odoo/addons/export_json/controller/main.py,sha256=bvM3cKA365eMBvXqycAON2Tnc73ZvnIqqqzmFbIELQU,16127
7
+ odoo/addons/export_json/docs/export_json_userguide.adoc,sha256=z1uwdxU16Ji4VOnDdLIzLnv7XqpKiAi5SZ2qSNbZorc,943
8
+ odoo/addons/export_json/docs/images/export_json2.png,sha256=4rKkrvcIgzRGFrtBwmL8CwheZLB3wDxK0vg3XnqOPUI,68768
9
+ odoo/addons/export_json/docs/images/export_json3_popup_export.png,sha256=LIDCvP2rhqSo-iXal0I46cnDWFo3tU2-jujQr-mMXAQ,102814
10
+ odoo/addons/export_json/docs/images/export_json_popup_export2.png,sha256=1a_UZ3jRqju5o3fhoiiySI8xauU2xwluk_9o79-m8nA,21395
11
+ odoo/addons/export_json/docs/images/process_export1.png,sha256=L_Eskhv0dNfZzkbBU4Vvh8AOspZPEsr3UIdJ_bMOUsg,61036
12
+ odoo/addons/export_json/models/__init__.py,sha256=TR1KOlZczvm4dhDPMpq1_ebc9B8c9q1fzGGV61pK7AI,24
13
+ odoo/addons/export_json/models/file_json.py,sha256=NY3s9TRVu23An5kNK3N5k4-AG9D4vS4PQdSlJlU3CKw,572
14
+ odoo/addons/export_json/static/src/css/description.css,sha256=ZUoNviTVfhLg3q_aQwuJyrJuh047wGy1t3GWtcoJFcE,3066
15
+ odoo/addons/export_json/tests/__init__.py,sha256=pyQlWluF3AhruvDbvI13etPuJs2ZZ75iAQY1lRZMsZE,31
16
+ odoo/addons/export_json/tests/test_export_json.py,sha256=3LfraZIWg8XIV-UqIA1vTx1KLsUgqYXXn2HO33rw2cM,32440
17
+ mangono_addon_export_json-18.0.1.0.4.dist-info/METADATA,sha256=pK2tVwv60ZECMgGgsT_qflOuWeO373yDAVqwFEmGLsU,552
18
+ mangono_addon_export_json-18.0.1.0.4.dist-info/WHEEL,sha256=ADTnST8WcJ5NkvOhz6ibaMSK2Os3Cjf9hXrb4uW3WtU,98
19
+ mangono_addon_export_json-18.0.1.0.4.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
20
+ mangono_addon_export_json-18.0.1.0.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: Mangono Wheel Builder0.4.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,47 @@
1
+ .. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
2
+ :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
3
+ :alt: License: LGPL-3
4
+
5
+ JSON Export
6
+ ===========
7
+ JSON Export system for Odoo 18 Community edition.
8
+
9
+ Configuration
10
+ =============
11
+ No configuration
12
+
13
+ Company
14
+ -------
15
+ * `Mangono <https://mangono.fr/>`__
16
+
17
+ License
18
+ -------
19
+ General Public License, Version 3 (LGPL v3).
20
+ (http://www.gnu.org/licenses/lgpl-3.0-standalone.html)
21
+
22
+ Credits
23
+ -------
24
+ Developer: (V18) Mangono , Contact: contact@mangono.fr
25
+
26
+ Contacts
27
+ --------
28
+ * Mail Contact : contact@mangono.fr
29
+ * Website : https://mangono.fr
30
+
31
+ Bug Tracker
32
+ -----------
33
+ In case of trouble, please contact us via our email address.
34
+
35
+ Maintainer
36
+ ==========
37
+ .. image:: static/description/assets/logo/mangono-logo-bleu.png
38
+ :scale: 10 %
39
+ :target: https://mangono.fr
40
+
41
+ This module is maintained by Mangono.
42
+
43
+ For support and more information, please visit `Our Website <https://mangono.fr/>`__
44
+
45
+ Further information
46
+ ===================
47
+ HTML Description: `<static/description/index.html>`__
@@ -0,0 +1 @@
1
+ JSON Export system for Odoo Community edition.
@@ -0,0 +1,2 @@
1
+ from . import models
2
+ from . import controller
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "JSON Export",
3
+ "version": "18.0.1.0.4",
4
+ "category": "Extra Tools",
5
+ "license": "AGPL-3",
6
+ "summary": "Add JSON export to all models similar to XLSX or CSV exports.",
7
+ "author": "Mangono",
8
+ "maintainers": "Mangono",
9
+ "support": "contact@mangono.fr",
10
+ "description": """
11
+ JSON Export
12
+ ===========
13
+ see README
14
+ """,
15
+ "website": "https://mangono.fr/",
16
+ "depends": ["base", "web", "jsonifier"],
17
+ "data": [],
18
+ "installable": True,
19
+ "images": ["static/description/banner.png"],
20
+ }
@@ -0,0 +1 @@
1
+ from . import main
@@ -0,0 +1,432 @@
1
+ import json
2
+ import logging
3
+ import operator
4
+ from typing import Any
5
+
6
+ import requests
7
+
8
+ from odoo import api, http, models
9
+ from odoo.http import content_disposition, request
10
+ from odoo.tools import json_default, osutil
11
+
12
+ from odoo.addons.web.controllers.export import (
13
+ Export,
14
+ ExportFormat,
15
+ )
16
+
17
+ _logger = logging.getLogger(__name__)
18
+
19
+
20
+ class JsonExport(Export):
21
+ @http.route("/web/export/formats", type="json", auth="user")
22
+ def formats(self):
23
+ res = super().formats()
24
+ res.append({"tag": "json", "label": "JSON"})
25
+ return res
26
+
27
+
28
+ class JsonExportFormat(ExportFormat, http.Controller):
29
+ @http.route("/web/export/json", type="http", auth="user")
30
+ def index(self, data: str) -> "requests.Response":
31
+ return self.base(data)
32
+
33
+ @property
34
+ def content_type(self) -> str:
35
+ """
36
+ :return: str, json format
37
+ """
38
+ return "text/json;charset=utf8"
39
+
40
+ @property
41
+ def extension(self) -> str:
42
+ """
43
+ Return extension for file, that will be generated later.
44
+ :return: json extension
45
+ """
46
+ return ".json"
47
+
48
+ @api.model
49
+ def format_data(self, fields) -> str:
50
+ for field in fields:
51
+ for key, value in field.items():
52
+ if type(value) is bytes:
53
+ field.update({key: "data:image/png;base64," + value.decode("utf-8")})
54
+ return json.dumps(fields, indent=4, default=json_default)
55
+
56
+ @api.model
57
+ def _insert_simple_field(self, fields_name: list, field_groups: dict, parser: list[dict]):
58
+ """
59
+ This function is used to add "simple" field like "partner_id", "name", in the parser that will be export.
60
+ Field name like "partner_id/child_ids, partner_id/child_ids/name" will be processed in another method.
61
+ :param fields_name: list of field name to export, display like ["partner_id", "partner_id/child_ids", ...].
62
+ :param field_groups: The dict is not return
63
+ :param parser: list of key's name field, define in a specific way
64
+ (exemple in function "define_parser" docstring).
65
+ :return: None, but "field_groups" and "parser" parameters are modified by side effect.
66
+ """
67
+ for field in fields_name:
68
+ name = field["name"]
69
+
70
+ if "/" in name:
71
+ parts = name.split("/")
72
+ main_field = parts[0]
73
+ subfield = "/".join(parts[1:])
74
+
75
+ if main_field not in field_groups:
76
+ field_groups[main_field] = []
77
+
78
+ field_groups[main_field].append({"name": subfield})
79
+ else:
80
+ parser.append(name)
81
+
82
+ @api.model
83
+ def _insert_nested_field(self, field_groups, parser):
84
+ for main_field, subfields in field_groups.items():
85
+ if any("/" in subfield["name"] for subfield in subfields):
86
+ nested_fields = [subfield for subfield in subfields]
87
+ nested_parser = self.define_parser(nested_fields)
88
+ parser.append((main_field, nested_parser))
89
+ else:
90
+ sub_names = [subfield["name"] for subfield in subfields]
91
+ parser.append((main_field, sub_names))
92
+
93
+ @api.model
94
+ def define_parser(self, fields_name: list[dict[str, str]]) -> tuple[str | dict[str, Any]]:
95
+ """
96
+ Args:
97
+ :param fields_name: list of dictionaries with two or three keys: 'label', 'name' which are field names
98
+ and optionally 'type', that is not use.
99
+ :return: list of field names to export. Example of parser:
100
+ parser = [
101
+ 'name',
102
+ 'number',
103
+ 'create_date',
104
+ ('partner_id', ['id', 'display_name', 'ref'])
105
+ ('line_id', ['id', ('product_id', ['name']), 'price_unit'])
106
+ ]
107
+ """
108
+ parser = []
109
+ field_groups = {}
110
+
111
+ self._insert_simple_field(fields_name, field_groups, parser)
112
+ self._insert_nested_field(field_groups, parser)
113
+
114
+ return parser
115
+
116
+ def base(self, data: str) -> "requests.Response":
117
+ params = json.loads(data)
118
+ model, fields, ids, domain, import_compat = operator.itemgetter(
119
+ "model", "fields", "ids", "domain", "import_compat"
120
+ )(params)
121
+ Model = request.env[model].with_context(import_compat=import_compat, **params.get("context", {}))
122
+ response_data = self.perform_json_export(domain, fields, ids, Model)
123
+
124
+ return request.make_response(
125
+ response_data,
126
+ headers=[
127
+ (
128
+ "Content-Disposition",
129
+ content_disposition(osutil.clean_filename(self.filename(model) + self.extension)),
130
+ ),
131
+ ("Content-Type", self.content_type),
132
+ ],
133
+ )
134
+
135
+ @api.model
136
+ def _convert_simple_list(self, fields_name) -> list[dict[str, str]]:
137
+ """
138
+ Use to convert a list of fields names that it is not a dict with 'name' key
139
+ :param fields_name: list of field names, either str or dict.
140
+ :return: converted list of field names in dict{'name' : 'field_name'}.
141
+ """
142
+ converted_fields_name = []
143
+
144
+ for element in fields_name:
145
+ if type(element) is not dict:
146
+ element_to_append = {"name": element}
147
+ else:
148
+ element_to_append = element
149
+ converted_fields_name.append(element_to_append)
150
+ return converted_fields_name
151
+
152
+ # region merge_lang_dict
153
+ @api.model
154
+ def process_keys(
155
+ self,
156
+ all_keys: set,
157
+ dicts_at_idx: dict,
158
+ merged_dict: dict,
159
+ parent_key: str = False,
160
+ ):
161
+ """
162
+ Traite un ensemble de clés en regroupant leurs valeurs depuis plusieurs dictionnaires par langue.
163
+
164
+ La méthode itère sur toutes les clés fournies et collecte leurs valeurs correspondantes
165
+ depuis différents dictionnaires organisés par langue. Pour chaque clé, on rassemble
166
+ les valeurs disponibles dans chaque langue et les transmet à "process_key_values" pour
167
+ un traitement ultérieur.
168
+ """
169
+ for key in all_keys:
170
+ key_values = {}
171
+ for lang, d in dicts_at_idx.items():
172
+ if key in d:
173
+ key_values[lang] = d[key]
174
+ self.process_key_values(key, key_values, merged_dict, parent_key)
175
+
176
+ @api.model
177
+ def process_key_values(
178
+ self,
179
+ current_key: str,
180
+ key_values: dict,
181
+ merged_item: dict,
182
+ parent_key: str = False,
183
+ ):
184
+ if key_values:
185
+ first_val = list(key_values.values())[0]
186
+ item_added = self.add_list_or_dict_on_merge_item(
187
+ current_key, first_val, key_values, merged_item, parent_key
188
+ )
189
+ if not item_added:
190
+ self.add_lang_key(current_key, first_val, key_values, merged_item, parent_key)
191
+
192
+ @api.model
193
+ def add_list_or_dict_on_merge_item(
194
+ self,
195
+ current_key,
196
+ current_val: list | dict,
197
+ values_by_lang: dict,
198
+ merged_item: dict,
199
+ parent_key: str = False,
200
+ ) -> bool:
201
+ """Permets de différencier l'ajout de dict ou de list dans le dictionnaire final.
202
+ Le paramètre merged_item est modifié par référence.
203
+ Retourne un boolean qui permet de ne pas continuer le process d'ajouts de données,
204
+ si la valeur courante a déjà été ajoutée.
205
+ """
206
+
207
+ # Si la clé courante et la clé parente sont définies, qu'elles ne sont pas identiques,
208
+ # et que la valeur courante est un dict (ou une list) ?
209
+ # Alors la clé parente devient la concaténation de la clé parente et courante,
210
+ # étant donnée que nous sommes dans des dicts imbriquées
211
+ if parent_key and current_key and parent_key != current_key and isinstance(current_val, dict):
212
+ parent_key = f"{parent_key}/{current_key}"
213
+ else:
214
+ parent_key = parent_key or current_key
215
+
216
+ # Gérer les listes de dictionnaires
217
+ if isinstance(current_val, list):
218
+ if current_val and isinstance(current_val[0], dict):
219
+ merged_item[current_key] = self._merge_list_of_dicts(values_by_lang, parent_key)
220
+ return True
221
+ # Gérer les dictionnaires imbriqués
222
+ elif isinstance(current_val, dict):
223
+ merged_item[current_key] = self._merge_nested_dict(values_by_lang, parent_key)
224
+ return True
225
+ return False
226
+
227
+ @api.model
228
+ def retrieve_all_keys(self, items: dict) -> set:
229
+ assert all(isinstance(v, dict) for v in items.values()), "All values must be dictionaries."
230
+ all_keys = set()
231
+ for vals in items.values():
232
+ all_keys.update(vals.keys())
233
+ return all_keys
234
+
235
+ @api.model
236
+ def add_unique_vals(self, key_values: dict) -> set:
237
+ """Vérifier si toutes les valeurs sont identiques"""
238
+ unique_values = set()
239
+ for val in key_values.values():
240
+ if isinstance(val, list | dict):
241
+ unique_values.add(str(val))
242
+ else:
243
+ unique_values.add(val)
244
+ return unique_values
245
+
246
+ @api.model
247
+ def get_country_code(self, lang_code):
248
+ if "_" in lang_code:
249
+ return lang_code.split("_")[1]
250
+ else:
251
+ return lang_code
252
+
253
+ @api.model
254
+ def add_lang_key(
255
+ self,
256
+ current_key: str,
257
+ first_val: str | list,
258
+ key_values: dict,
259
+ merged_dict: dict,
260
+ parent_key: str = False,
261
+ ):
262
+ """Permets d'ajouter au dict final, soit une valeur avec une clé unique, s'il n'existe pas
263
+ de différence dans les dict de chaque langue, ou bien une clé par lang, s'il y a des différences.
264
+ """
265
+ # Vérifier si toutes les valeurs sont identiques
266
+ unique_values = self.add_unique_vals(key_values)
267
+ if len(unique_values) <= 1:
268
+ # Valeurs identiques
269
+ merged_dict[current_key] = first_val
270
+ else:
271
+ # On ajoute un ensemble {key : val}, avec pour key la clé courante sans ajout, et pour valeur,
272
+ # le chemin de la key.
273
+ if parent_key:
274
+ merged_dict[current_key] = f"{parent_key}/{current_key}"
275
+ else:
276
+ merged_dict[current_key] = current_key
277
+
278
+ # Valeurs différentes, créer des clés avec suffixes, qui serviront provisoirement à stocker les traductions
279
+ # de la clé
280
+ for lang, val in key_values.items():
281
+ lang_suffix = self.get_country_code(lang)
282
+ merged_dict[f"{current_key}_{lang_suffix}"] = val
283
+
284
+ @api.model
285
+ def merge_multilingual_dicts(self, multilingual_data: dict) -> list:
286
+ """
287
+ Fusionne les dictionnaires de différentes langues en un seul dictionnaire.
288
+ Pour chaque clé:
289
+ - Si toutes les valeurs sont identiques -> garde la clé originale
290
+ - Si les valeurs diffèrent -> crée des clés suffixées par le code langue
291
+ - Si la valeur est une liste de dicts -> fusionne récursivement
292
+ - Si la valeur est un dict -> fusionne récursivement
293
+ """
294
+
295
+ languages = list(multilingual_data.keys())
296
+
297
+ if not languages:
298
+ return []
299
+
300
+ base_lang = languages[0]
301
+ base_data = multilingual_data[base_lang]
302
+
303
+ merged_data = []
304
+
305
+ for idx, base_item in enumerate(base_data):
306
+ merged_item = {}
307
+
308
+ # Récupérer tous les éléments correspondants dans les autres langues
309
+ lang_items = {base_lang: base_item}
310
+ for lang in languages[1:]:
311
+ if idx < len(multilingual_data[lang]):
312
+ lang_items[lang] = multilingual_data[lang][idx]
313
+
314
+ # Analyser chaque clé
315
+ all_keys = self.retrieve_all_keys(lang_items)
316
+
317
+ # Récupérer les valeurs pour cette clé dans toutes les langues
318
+ self.process_keys(all_keys, lang_items, merged_item)
319
+ merged_data.append(merged_item)
320
+
321
+ return merged_data
322
+
323
+ @api.model
324
+ def _merge_nested_dict(self, values_by_lang: dict, parent_key: str = False) -> dict:
325
+ """
326
+ Fusionne des dictionnaires imbriqués provenant de différentes langues.
327
+ Args:
328
+ parent_key: str, key parent du dict imbriqué
329
+ values_by_lang: dict {lang_code: dict}
330
+ Returns:
331
+ dict: dictionnaire fusionné
332
+ """
333
+ merged_dict = {}
334
+
335
+ # Récupérer toutes les clés possibles
336
+ all_keys = self.retrieve_all_keys(values_by_lang)
337
+
338
+ # Pour chaque clé, comparer les valeurs
339
+ for key in all_keys:
340
+ key_values = {}
341
+ for lang, d in values_by_lang.items():
342
+ if isinstance(d, dict) and key in d:
343
+ key_values[lang] = d[key]
344
+
345
+ if key_values:
346
+ # Modification du paramètre merged_dict par référence
347
+ first_val = list(key_values.values())[0]
348
+ self.add_lang_key(key, first_val, key_values, merged_dict, parent_key)
349
+
350
+ return merged_dict
351
+
352
+ @api.model
353
+ def _merge_list_of_dicts(self, values_by_lang: dict, parent_key: str = False) -> list[dict]:
354
+ """
355
+ Fusionne des listes de dictionnaires provenant de différentes langues.
356
+
357
+ Args:
358
+ values_by_lang: dict {lang_code: [dict1, dict2, ...]}
359
+ parent_key : str
360
+
361
+ Returns:
362
+ list: liste fusionnée de dictionnaires
363
+ """
364
+ languages = list(values_by_lang.keys())
365
+
366
+ if not languages:
367
+ return []
368
+
369
+ # Utiliser la première langue comme référence pour la longueur
370
+ base_lang = languages[0]
371
+ base_list = values_by_lang[base_lang]
372
+
373
+ merged_list = []
374
+
375
+ # Pour chaque index dans la liste
376
+ for idx in range(len(base_list)):
377
+ merged_dict = {}
378
+
379
+ # Récupérer tous les dictionnaires à cet index
380
+ dicts_at_idx = {}
381
+ for lang in languages:
382
+ if idx < len(values_by_lang[lang]):
383
+ dicts_at_idx[lang] = values_by_lang[lang][idx]
384
+
385
+ # Récupérer toutes les clés possibles
386
+ all_keys = self.retrieve_all_keys(dicts_at_idx)
387
+ self.process_keys(all_keys, dicts_at_idx, merged_dict, parent_key)
388
+ merged_list.append(merged_dict)
389
+
390
+ return merged_list
391
+
392
+ # endregion merge_lang_dict
393
+
394
+ def perform_json_export(
395
+ self,
396
+ domain: list[list[Any]],
397
+ field_names: list[str],
398
+ ids: list[int],
399
+ model: models.BaseModel,
400
+ langs_code: list[str] = False,
401
+ ) -> str:
402
+ """
403
+ Method to prepare and format json data for the export.
404
+
405
+ Args:
406
+ :param domain: condition (domain) needed for the search method
407
+ :param field_names: list of dictionaries with two keys : 'label' and 'name' which are field names
408
+ :param ids: list with ids of selected records
409
+ :param model: model on which the export is done
410
+ :param langs_code: languages code
411
+ :return: string with json_data
412
+ """
413
+ if not model._is_an_ordinary_table():
414
+ field_names = [field for field in field_names if field != "id"]
415
+ field_names = self._convert_simple_list(field_names)
416
+
417
+ records = model.browse(ids) if ids else model.search(domain, offset=0, limit=False, order=False)
418
+ parser = self.define_parser(field_names)
419
+
420
+ if not langs_code:
421
+ langs_code = [request.env.context.get("lang")] or [request.env.user.lang]
422
+ tz = request.env.context.get("tz") or request.env.user.tz
423
+ if len(langs_code) == 1:
424
+ result = records.with_context(lang=langs_code[0], tz=tz).jsonify(parser)
425
+ return self.format_data(result)
426
+
427
+ result = {}
428
+ for lang in langs_code:
429
+ result[lang] = records.with_context(lang=lang, tz=tz).jsonify(parser)
430
+ merged_list = self.merge_multilingual_dicts(result)
431
+ response_data = self.format_data(merged_list)
432
+ return response_data
@@ -0,0 +1,25 @@
1
+ = Guide d'utilisation de l'export JSON
2
+
3
+ == Présentation de la fonctionnalité
4
+
5
+ L'export JSON permet, comme son nom l'indique, d'exporter les données d'un record en format JSON.
6
+
7
+ Ce type d'export peut être utilisé par exemple si le connecteur Carbone.io est utilisé, car il a besoin de recevoir les données à intégrer au document sous le format JSON.
8
+
9
+ == Utilisation de la fonctionnalité
10
+
11
+ - Dans l'interface Odoo, aller sur une vue Liste > sélectionner au moins un record :
12
+
13
+ image::images/process_export1.png[]
14
+
15
+ - Ensuite, cliquer sur _Action_ -> _Exporter_ , cette action ouvre un popup :
16
+
17
+ image::images/export_json2.png[]
18
+
19
+ image::images/export_json3_popup_export.png[]
20
+
21
+ - Sélectionner le format d'exportation de type _JSON_ , insérer tous les champs à exporter, créer un modèle d'export et le sauvegarder :
22
+
23
+ image::images/export_json_popup_export2.png[]
24
+
25
+ - Cliquer sur _Exporter_ , le fichier en format JSON est téléchargé.
@@ -0,0 +1 @@
1
+ from . import file_json
@@ -0,0 +1,20 @@
1
+ import json
2
+
3
+ from odoo import models
4
+
5
+ from odoo.addons.base_import import models as model_import
6
+
7
+ model_import.base_import.FILE_TYPE_DICT["application/json"] = ("json", True, None)
8
+ model_import.base_import.EXTENSIONS[".json"] = True
9
+
10
+
11
+ class BaseImportJSON(models.TransientModel):
12
+ _inherit = "base_import.import"
13
+
14
+ def _read_json(self, record, options):
15
+ items = json.loads(record.file)
16
+ if items:
17
+ headers = items[0].keys()
18
+ yield headers
19
+ for item in items:
20
+ yield [item[header] for header in headers]