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.
- mangono_addon_export_json-18.0.1.0.4.dist-info/METADATA +15 -0
- mangono_addon_export_json-18.0.1.0.4.dist-info/RECORD +20 -0
- mangono_addon_export_json-18.0.1.0.4.dist-info/WHEEL +5 -0
- mangono_addon_export_json-18.0.1.0.4.dist-info/top_level.txt +1 -0
- odoo/addons/export_json/README.rst +47 -0
- odoo/addons/export_json/README_PYPI.md +1 -0
- odoo/addons/export_json/__init__.py +2 -0
- odoo/addons/export_json/__manifest__.py +20 -0
- odoo/addons/export_json/controller/__init__.py +1 -0
- odoo/addons/export_json/controller/main.py +432 -0
- odoo/addons/export_json/docs/export_json_userguide.adoc +25 -0
- odoo/addons/export_json/docs/images/export_json2.png +0 -0
- odoo/addons/export_json/docs/images/export_json3_popup_export.png +0 -0
- odoo/addons/export_json/docs/images/export_json_popup_export2.png +0 -0
- odoo/addons/export_json/docs/images/process_export1.png +0 -0
- odoo/addons/export_json/models/__init__.py +1 -0
- odoo/addons/export_json/models/file_json.py +20 -0
- odoo/addons/export_json/static/src/css/description.css +187 -0
- odoo/addons/export_json/tests/__init__.py +1 -0
- odoo/addons/export_json/tests/test_export_json.py +821 -0
|
@@ -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 @@
|
|
|
1
|
+
odoo
|
|
@@ -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,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é.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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]
|