From af98af3f4eb8d456e214a3574c548d7ea2140837 Mon Sep 17 00:00:00 2001
From: Alexis de Lattre
Date: Sat, 7 Oct 2023 00:33:03 +0200
Subject: [PATCH 1/6] Add module product_import_helper
Move code from partner_import_helper to import_helper_base
---
import_helper_base/README.rst | 8 +
import_helper_base/__init__.py | 1 +
import_helper_base/__manifest__.py | 21 +++
.../security/ir.model.access.csv | 2 +
import_helper_base/wizards/__init__.py | 1 +
.../wizards/import_show_logs.py | 142 ++++++++++++++++++
.../wizards/import_show_logs_view.xml | 21 +++
7 files changed, 196 insertions(+)
create mode 100644 import_helper_base/README.rst
create mode 100644 import_helper_base/__init__.py
create mode 100644 import_helper_base/__manifest__.py
create mode 100644 import_helper_base/security/ir.model.access.csv
create mode 100644 import_helper_base/wizards/__init__.py
create mode 100644 import_helper_base/wizards/import_show_logs.py
create mode 100644 import_helper_base/wizards/import_show_logs_view.xml
diff --git a/import_helper_base/README.rst b/import_helper_base/README.rst
new file mode 100644
index 0000000..0cb9bf7
--- /dev/null
+++ b/import_helper_base/README.rst
@@ -0,0 +1,8 @@
+==================
+Import Helper Base
+==================
+
+This is a technical module that contains common code for several import helper modules:
+
+* partner_import_helper
+* product_import_helper
diff --git a/import_helper_base/__init__.py b/import_helper_base/__init__.py
new file mode 100644
index 0000000..5cb1c49
--- /dev/null
+++ b/import_helper_base/__init__.py
@@ -0,0 +1 @@
+from . import wizards
diff --git a/import_helper_base/__manifest__.py b/import_helper_base/__manifest__.py
new file mode 100644
index 0000000..33cb935
--- /dev/null
+++ b/import_helper_base/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2023 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ 'name': 'Import Helper Base',
+ 'version': '16.0.1.0.0',
+ 'category': 'Extra Tools',
+ 'license': 'AGPL-3',
+ 'summary': 'Common code for all import helper modules',
+ 'author': 'Akretion',
+ 'website': 'https://github.com/akretion/odoo-import-helper',
+ 'depends': [
+ 'base',
+ ],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizards/import_show_logs_view.xml',
+ ],
+ 'installable': True,
+}
diff --git a/import_helper_base/security/ir.model.access.csv b/import_helper_base/security/ir.model.access.csv
new file mode 100644
index 0000000..6415e08
--- /dev/null
+++ b/import_helper_base/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_import_show_logs,Full access on import.show.logs wizard,model_import_show_logs,base.group_user,1,1,1,1
diff --git a/import_helper_base/wizards/__init__.py b/import_helper_base/wizards/__init__.py
new file mode 100644
index 0000000..bec5909
--- /dev/null
+++ b/import_helper_base/wizards/__init__.py
@@ -0,0 +1 @@
+from . import import_show_logs
diff --git a/import_helper_base/wizards/import_show_logs.py b/import_helper_base/wizards/import_show_logs.py
new file mode 100644
index 0000000..a8ea130
--- /dev/null
+++ b/import_helper_base/wizards/import_show_logs.py
@@ -0,0 +1,142 @@
+# Copyright 2023 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import api, fields, models, tools, _
+from odoo.exceptions import UserError
+from collections import defaultdict
+from datetime import datetime
+
+import logging
+logger = logging.getLogger(__name__)
+
+try:
+ import openai
+except ImportError:
+ logger.debug('Cannot import openai')
+
+class ImportShowLogs(models.TransientModel):
+ _name = "import.show.logs"
+ _description = "Pop-up to show warnings after import"
+
+ logs = fields.Html(readonly=True)
+
+ @api.model
+ def _import_speedy(self, chatgpt=False):
+ logger.debug('Start to prepare import speedy')
+ speedy = {
+ 'chatgpt': chatgpt,
+ 'field2label': {},
+ 'logs': [],
+ # 'logs' should contain a list of dict :
+ # {'msg': 'Checksum IBAN wrong',
+ # 'value': 'FR9879834739',
+ # 'vals': vals, # used to get the line
+ # (and display_name if partner has been created)
+ # 'field': 'res.partner,email',
+ # 'reset': True, # True if the data is NOT imported in Odoo
+ # }
+ }
+ if chatgpt:
+ openai_api_key = tools.config.get('openai_api_key', False)
+ if not openai_api_key:
+ raise UserError(_(
+ "Missing entry openai_api_key in the Odoo server configuration file."))
+ openai.api_key = openai_api_key
+ speedy['openai_tokens'] = 0
+ return speedy
+
+ def _field_label(self, field, speedy):
+ if field not in speedy['field2label']:
+ field_split = field.split(',')
+ ofield = self.env['ir.model.fields'].search([
+ ('model', '=', field_split[0]),
+ ('name', '=', field_split[1]),
+ ], limit=1)
+ if ofield:
+ speedy['field2label'][field] = ofield.field_description
+ else:
+ speedy['field2label'][field] = '%s (%s)' % (
+ field_split[1], field_split[0])
+ return speedy['field2label'][field]
+
+ def _import_logs2html(self, speedy):
+ line2logs = defaultdict(list)
+ field2logs = defaultdict(list)
+ for log in speedy['logs']:
+ if log['vals'].get('line'):
+ line2logs[log['vals']['line']].append(log)
+ if log.get('field'):
+ field2logs[log['field']].append(log)
+ html = 'For the logs in red, the data was not imported in Odoo
'
+ if speedy.get('chatgpt'):
+ html += '%d OpenAI tokens where used
' % speedy['openai_tokens']
+ html += 'Logs per line
'
+ for line, logs in line2logs.items():
+ log_labels = []
+ for log in logs:
+ log_labels.append(
+ '%s: %s - %s' % (
+ log.get('reset') and 'red' or 'black',
+ self._field_label(log['field'], speedy),
+ log['value'],
+ log['msg'],
+ ))
+ h3 = 'Line %s' % line
+ if log['vals'].get('id'):
+ h3 += ': %s (ID %d)' % (log['vals']['display_name'], log['vals']['id'])
+ html += '%s
\n
' % (h3, '\n'.join(log_labels))
+ html += 'Logs per field
'
+ for field, logs in field2logs.items():
+ log_labels = []
+ for log in logs:
+ line_label = 'Line %s' % log['vals'].get('line', 'unknown')
+ if log['vals'].get('id'):
+ line_label += ' (%s ID %d)' % (log['vals']['display_name'], log['vals']['id'])
+ log_labels.append(
+ '%s: %s - %s' % (
+ log.get('reset') and 'red' or 'black',
+ line_label,
+ log['value'],
+ log['msg'],
+ ))
+ html += '%s
\n' % (
+ self._field_label(field, speedy), '\n'.join(log_labels))
+ return html
+
+ def _import_result_action(self, speedy):
+ action = {
+ 'name': 'Result',
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'import.show.logs',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': dict(self._context, default_logs=self._import_logs2html(speedy)),
+ }
+ return action
+
+ def _prepare_create_date(self, vals, speedy):
+ create_date = vals.get('create_date')
+ create_date_dt = False
+ if isinstance(create_date, str) and len(create_date) == 10:
+ try:
+ create_date_dt = datetime.strptime(create_date, '%Y-%m-%d')
+ except Exception as e:
+ speedy['logs'].append({
+ 'msg': "Failed to convert '%s' to datetime: %s" % (create_date, e),
+ 'value': vals['create_date'],
+ 'vals': vals,
+ 'field': 'product.product,create_date',
+ 'reset': True,
+ })
+ elif isinstance(create_date, datetime):
+ create_date_dt = create_date
+ if create_date_dt and create_date_dt.date() > fields.Date.context_today(self):
+ speedy['logs'].append({
+ 'msg': 'create_date %s cannot be in the future' % create_date_dt,
+ 'value': create_date,
+ 'vals': vals,
+ 'field': 'product.product,create_date',
+ 'reset': True,
+ })
+ return create_date_dt
diff --git a/import_helper_base/wizards/import_show_logs_view.xml b/import_helper_base/wizards/import_show_logs_view.xml
new file mode 100644
index 0000000..eaf1735
--- /dev/null
+++ b/import_helper_base/wizards/import_show_logs_view.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ import.show.logs
+
+
+
+
+
From 7fac1ee820a35841072d48e9512011273c63d679 Mon Sep 17 00:00:00 2001
From: Alexis de Lattre
Date: Mon, 15 Jan 2024 18:09:58 +0100
Subject: [PATCH 2/6] Adapt to changes in openai lib
---
import_helper_base/wizards/import_show_logs.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/import_helper_base/wizards/import_show_logs.py b/import_helper_base/wizards/import_show_logs.py
index a8ea130..a06ea8e 100644
--- a/import_helper_base/wizards/import_show_logs.py
+++ b/import_helper_base/wizards/import_show_logs.py
@@ -11,7 +11,7 @@
logger = logging.getLogger(__name__)
try:
- import openai
+ from openai import OpenAI
except ImportError:
logger.debug('Cannot import openai')
@@ -42,7 +42,7 @@ def _import_speedy(self, chatgpt=False):
if not openai_api_key:
raise UserError(_(
"Missing entry openai_api_key in the Odoo server configuration file."))
- openai.api_key = openai_api_key
+ speedy['openai_client'] = OpenAI(api_key=openai_api_key)
speedy['openai_tokens'] = 0
return speedy
From b771dddb330a71b286f5fe4edbfa5db783217743 Mon Sep 17 00:00:00 2001
From: Alexis de Lattre
Date: Tue, 16 Jan 2024 00:35:22 +0100
Subject: [PATCH 3/6] import_helper module: full code reorganisation, to make
it easier to import products and suppliers at the same time
---
import_helper_base/__manifest__.py | 2 +-
.../security/ir.model.access.csv | 2 +-
import_helper_base/wizards/__init__.py | 2 +-
.../{import_show_logs.py => import_helper.py} | 106 +++++++++---------
...w_logs_view.xml => import_helper_view.xml} | 4 +-
5 files changed, 61 insertions(+), 55 deletions(-)
rename import_helper_base/wizards/{import_show_logs.py => import_helper.py} (55%)
rename import_helper_base/wizards/{import_show_logs_view.xml => import_helper_view.xml} (84%)
diff --git a/import_helper_base/__manifest__.py b/import_helper_base/__manifest__.py
index 33cb935..b6f40d2 100644
--- a/import_helper_base/__manifest__.py
+++ b/import_helper_base/__manifest__.py
@@ -15,7 +15,7 @@
],
'data': [
'security/ir.model.access.csv',
- 'wizards/import_show_logs_view.xml',
+ 'wizards/import_helper_view.xml',
],
'installable': True,
}
diff --git a/import_helper_base/security/ir.model.access.csv b/import_helper_base/security/ir.model.access.csv
index 6415e08..0bba804 100644
--- a/import_helper_base/security/ir.model.access.csv
+++ b/import_helper_base/security/ir.model.access.csv
@@ -1,2 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_import_show_logs,Full access on import.show.logs wizard,model_import_show_logs,base.group_user,1,1,1,1
+access_import_helper_full,Full access on import.helper wizard,model_import_helper,base.group_user,1,1,1,1
diff --git a/import_helper_base/wizards/__init__.py b/import_helper_base/wizards/__init__.py
index bec5909..d07f7a6 100644
--- a/import_helper_base/wizards/__init__.py
+++ b/import_helper_base/wizards/__init__.py
@@ -1 +1 @@
-from . import import_show_logs
+from . import import_helper
diff --git a/import_helper_base/wizards/import_show_logs.py b/import_helper_base/wizards/import_helper.py
similarity index 55%
rename from import_helper_base/wizards/import_show_logs.py
rename to import_helper_base/wizards/import_helper.py
index a06ea8e..e810686 100644
--- a/import_helper_base/wizards/import_show_logs.py
+++ b/import_helper_base/wizards/import_helper.py
@@ -15,20 +15,22 @@
except ImportError:
logger.debug('Cannot import openai')
-class ImportShowLogs(models.TransientModel):
- _name = "import.show.logs"
- _description = "Pop-up to show warnings after import"
+
+class ImportHelper(models.TransientModel):
+ _name = "import.helper"
+ _description = "Helper to import data in Odoo"
logs = fields.Html(readonly=True)
@api.model
- def _import_speedy(self, chatgpt=False):
+ def _prepare_speedy(self, aiengine='chatgpt'):
logger.debug('Start to prepare import speedy')
speedy = {
- 'chatgpt': chatgpt,
+ 'aiengine': aiengine,
'field2label': {},
- 'logs': [],
- # 'logs' should contain a list of dict :
+ 'logs': {},
+ # 'logs' is a dict {'res.partner': [], 'product.product': []}
+ # where the value is a list of dict :
# {'msg': 'Checksum IBAN wrong',
# 'value': 'FR9879834739',
# 'vals': vals, # used to get the line
@@ -37,7 +39,7 @@ def _import_speedy(self, chatgpt=False):
# 'reset': True, # True if the data is NOT imported in Odoo
# }
}
- if chatgpt:
+ if aiengine == 'chatgpt':
openai_api_key = tools.config.get('openai_api_key', False)
if not openai_api_key:
raise UserError(_(
@@ -60,58 +62,62 @@ def _field_label(self, field, speedy):
field_split[1], field_split[0])
return speedy['field2label'][field]
- def _import_logs2html(self, speedy):
- line2logs = defaultdict(list)
- field2logs = defaultdict(list)
- for log in speedy['logs']:
- if log['vals'].get('line'):
- line2logs[log['vals']['line']].append(log)
- if log.get('field'):
- field2logs[log['field']].append(log)
+ def _convert_logs2html(self, speedy):
html = 'For the logs in red, the data was not imported in Odoo
'
- if speedy.get('chatgpt'):
+ if speedy.get('aiengine') == 'chatgpt':
html += '%d OpenAI tokens where used
' % speedy['openai_tokens']
- html += 'Logs per line
'
- for line, logs in line2logs.items():
- log_labels = []
- for log in logs:
- log_labels.append(
- '%s: %s - %s' % (
- log.get('reset') and 'red' or 'black',
- self._field_label(log['field'], speedy),
- log['value'],
- log['msg'],
- ))
- h3 = 'Line %s' % line
- if log['vals'].get('id'):
- h3 += ': %s (ID %d)' % (log['vals']['display_name'], log['vals']['id'])
- html += '%s
\n' % (h3, '\n'.join(log_labels))
- html += 'Logs per field
'
- for field, logs in field2logs.items():
- log_labels = []
- for log in logs:
- line_label = 'Line %s' % log['vals'].get('line', 'unknown')
+ for obj_name, log_list in speedy['logs'].items():
+ obj_rec = self.env['ir.model'].search([('model', '=', obj_name)], limit=1)
+ assert obj_rec
+ html += '%s
' % obj_rec.name
+ line2logs = defaultdict(list)
+ field2logs = defaultdict(list)
+ for log in log_list:
+ if log['vals'].get('line'):
+ line2logs[log['vals']['line']].append(log)
+ if log.get('field'):
+ field2logs[log['field']].append(log)
+ html += 'Logs per line
'
+ for line, logs in line2logs.items():
+ log_labels = []
+ for log in logs:
+ log_labels.append(
+ '%s: %s - %s' % (
+ log.get('reset') and 'red' or 'black',
+ self._field_label(log['field'], speedy),
+ log['value'],
+ log['msg'],
+ ))
+ h3 = 'Line %s' % line
if log['vals'].get('id'):
- line_label += ' (%s ID %d)' % (log['vals']['display_name'], log['vals']['id'])
- log_labels.append(
- '%s: %s - %s' % (
- log.get('reset') and 'red' or 'black',
- line_label,
- log['value'],
- log['msg'],
- ))
- html += '%s
\n' % (
- self._field_label(field, speedy), '\n'.join(log_labels))
+ h3 += ': %s (ID %d)' % (log['vals']['display_name'], log['vals']['id'])
+ html += '%s
\n' % (h3, '\n'.join(log_labels))
+ html += 'Logs per field
'
+ for field, logs in field2logs.items():
+ log_labels = []
+ for log in logs:
+ line_label = 'Line %s' % log['vals'].get('line', 'unknown')
+ if log['vals'].get('id'):
+ line_label += ' (%s ID %d)' % (log['vals']['display_name'], log['vals']['id'])
+ log_labels.append(
+ '%s: %s - %s' % (
+ log.get('reset') and 'red' or 'black',
+ line_label,
+ log['value'],
+ log['msg'],
+ ))
+ html += '%s
\n' % (
+ self._field_label(field, speedy), '\n'.join(log_labels))
return html
- def _import_result_action(self, speedy):
+ def _result_action(self, speedy):
action = {
'name': 'Result',
'type': 'ir.actions.act_window',
- 'res_model': 'import.show.logs',
+ 'res_model': 'import.helper',
'view_mode': 'form',
'target': 'new',
- 'context': dict(self._context, default_logs=self._import_logs2html(speedy)),
+ 'context': dict(self._context, default_logs=self._convert_logs2html(speedy)),
}
return action
diff --git a/import_helper_base/wizards/import_show_logs_view.xml b/import_helper_base/wizards/import_helper_view.xml
similarity index 84%
rename from import_helper_base/wizards/import_show_logs_view.xml
rename to import_helper_base/wizards/import_helper_view.xml
index eaf1735..c30db50 100644
--- a/import_helper_base/wizards/import_show_logs_view.xml
+++ b/import_helper_base/wizards/import_helper_view.xml
@@ -5,8 +5,8 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
-
- import.show.logs
+
+ import.helper