summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2018-03-15 09:52:32 +0100
committerÉtienne Loks <etienne.loks@iggdrasil.net>2018-03-27 17:52:07 +0200
commit9e83c044636aee77c2f8f149ba2356674b071f8f (patch)
treea4071ff51a08618ad9d8c3f4fec1d0dc7869ca11 /ishtar_common
parent3fbf4471d52727750185f21052d777c119a50de0 (diff)
downloadIshtar-9e83c044636aee77c2f8f149ba2356674b071f8f.tar.bz2
Ishtar-9e83c044636aee77c2f8f149ba2356674b071f8f.zip
Step by step import - WIP - refs #3975
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/data_importer.py45
-rw-r--r--ishtar_common/models_imports.py28
-rw-r--r--ishtar_common/templates/ishtar/import_step_by_step.html50
-rw-r--r--ishtar_common/urls.py2
-rw-r--r--ishtar_common/views.py84
5 files changed, 185 insertions, 24 deletions
diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py
index ffe6c221d..420e38008 100644
--- a/ishtar_common/data_importer.py
+++ b/ishtar_common/data_importer.py
@@ -18,9 +18,7 @@
# See the file COPYING for details.
import copy
-import csv
import datetime
-import io
import os
import logging
import re
@@ -715,7 +713,6 @@ class Importer(object):
DESC = ""
LINE_FORMAT = []
OBJECT_CLS = None
- IMPORTED_LINE_FIELD = None
UNICITY_KEYS = []
# if set only models inside this list can be created
MODEL_CREATION_LIMIT = []
@@ -873,6 +870,8 @@ class Importer(object):
self.line_format = copy.copy(self.LINE_FORMAT)
self.import_instance = import_instance
self.archive = None
+ self.simulate = False
+ self.current_csv_line = None
self.conservative_import = conservative_import
# for a conservative_import UNICITY_KEYS should be defined
assert not self.conservative_import or bool(self.UNICITY_KEYS)
@@ -953,12 +952,19 @@ class Importer(object):
import_instance=self.import_instance,
user=user)
+ def get_formaters(self):
+ return self.line_format
+
def importation(self, table, initialize=True, choose_default=False,
- user=None):
+ user=None, line_to_process=None, simulate=False):
if initialize:
self.initialize(table, self.output,
choose_default=choose_default, user=user)
- self._importation(table)
+ self.simulate = simulate
+ return self._importation(table, line_to_process=line_to_process)
+
+ def get_current_values(self, obj):
+ return obj
def _associate_db_target_to_formaters(self):
if not self.import_instance:
@@ -1023,7 +1029,7 @@ class Importer(object):
current_data = current_data[key]
return data
- def _importation(self, table):
+ def _importation(self, table, line_to_process=None):
self.match_table = {}
table = list(table)
if not table or not table[0]:
@@ -1034,6 +1040,7 @@ class Importer(object):
self.errors = []
self.validity = []
self.number_imported = 0
+ idx_last_col = 0
# index of the last required column
for idx_last_col, formater in enumerate(reversed(self.line_format)):
if formater and formater.required:
@@ -1053,7 +1060,13 @@ class Importer(object):
total = len(table)
if self.output == 'cli':
sys.stdout.write("\n")
+ results = []
for idx_line, line in enumerate(table):
+ if line_to_process is not None:
+ if line_to_process != idx_line:
+ continue
+ if idx_line > line_to_process:
+ return results
if self.output == 'cli':
left = None
if idx_line > 10:
@@ -1067,9 +1080,10 @@ class Importer(object):
sys.stdout.write(txt.encode('utf-8'))
sys.stdout.flush()
try:
- self._line_processing(idx_line, line)
+ results.append(self._line_processing(idx_line, line))
except ImporterError, msg:
self.errors.append((idx_line, None, msg))
+ return results
def _line_processing(self, idx_line, line):
self.idx_line = idx_line
@@ -1084,12 +1098,10 @@ class Importer(object):
self._item_post_processing = []
data = {}
- # keep in database the raw line for testing purpose
- if self.IMPORTED_LINE_FIELD:
- output = io.StringIO()
- writer = csv.writer(output)
- writer.writerow(line)
- data[self.IMPORTED_LINE_FIELD] = output.getvalue()
+ self.current_csv_line = None
+ # raw line for simulation
+ if self.simulate:
+ self.current_csv_line = line
n = datetime.datetime.now()
logger.debug('%s - Processing line %d' % (unicode(n - self.now),
@@ -1117,6 +1129,8 @@ class Importer(object):
n2 = n
if self.test:
return
+ if self.simulate:
+ return data
# manage unicity of items (mainly for updates)
if 'history_modifier' in get_all_field_names(self.OBJECT_CLS):
data['history_modifier'] = self.history_modifier
@@ -1188,6 +1202,7 @@ class Importer(object):
formater.post_process(obj, data, val, owner=self.history_modifier)
self.post_processing(idx_line, obj)
+ return data
def _row_processing(self, c_row, idx_col, idx_line, val, data):
if idx_col >= len(self.line_format):
@@ -1557,7 +1572,9 @@ class Importer(object):
self.errors.append((self.idx_line, None, msg))
data[attribute] = None
- def get_object(self, cls, data, path=[]):
+ def get_object(self, cls, data, path=None):
+ if not path:
+ path = []
m2ms = []
if type(data) != dict:
# if data is not a dict we don't know what to do
diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py
index c3fbaa92d..10124b024 100644
--- a/ishtar_common/models_imports.py
+++ b/ishtar_common/models_imports.py
@@ -170,7 +170,7 @@ class ImporterType(models.Model):
if column.export_field_name:
LINE_EXPORT_FORMAT.append(
ImportFormater(column.export_field_name,
- label=column.label)
+ label=column.label)
)
continue
force_news = []
@@ -196,6 +196,7 @@ class ImporterType(models.Model):
formater_kwargs['label'] = column.label
formater_kwargs['required'] = column.required
formater_kwargs['force_new'] = force_news
+ formater_kwargs['comment'] = column.description
if column.export_field_name:
formater_kwargs['export_field_name'] = [
column.export_field_name]
@@ -865,9 +866,11 @@ class Import(models.Model):
if self.state == 'A':
actions.append(('A', _(u"Re-analyse")))
actions.append(('I', _(u"Launch import")))
+ actions.append(('IS', _(u"Step by step import")))
if self.state in ('F', 'FE'):
actions.append(('A', _(u"Re-analyse")))
actions.append(('I', _(u"Re-import")))
+ actions.append(('IS', _(u"Step by step import")))
actions.append(('AC', _(u"Archive")))
if self.state == 'AC':
actions.append(('A', _(u"Unarchive")))
@@ -946,21 +949,22 @@ class Import(models.Model):
self.save()
return delayed_import(self.pk, session_key)
- def importation(self, session_key=None):
+ def importation(self, session_key=None, line_to_process=None,
+ simulate=False, return_importer_and_data=False):
self.state = 'IP'
self.end_date = datetime.datetime.now()
self.save()
importer = self.get_importer_instance()
try:
- importer.importation(self.data_table, user=self.user)
+ data = importer.importation(
+ self.data_table, user=self.user,
+ line_to_process=line_to_process, simulate=simulate)
except IOError:
+ error_message = unicode(_(u"Error on imported file: {}")).format(
+ self.imported_file)
+ importer.errors = [error_message]
if session_key:
- put_session_message(
- session_key,
- unicode(_(u"Error on imported file: {}")).format(
- self.imported_file),
- "warning"
- )
+ put_session_message(session_key, error_message, "warning")
ids = get_session_var(session_key, 'current_import_id')
if not ids:
ids = []
@@ -968,7 +972,9 @@ class Import(models.Model):
put_session_var(session_key, 'current_import_id', ids)
self.state = 'FE'
self.save()
- return
+ if not return_importer_and_data:
+ return
+ return importer, None
# result file
filename = slugify(self.importer_type.name)
now = datetime.datetime.now().isoformat('-').replace(':', '')
@@ -1006,6 +1012,8 @@ class Import(models.Model):
)
self.end_date = datetime.datetime.now()
self.save()
+ if return_importer_and_data:
+ return importer, data
def archive(self):
self.state = 'AC'
diff --git a/ishtar_common/templates/ishtar/import_step_by_step.html b/ishtar_common/templates/ishtar/import_step_by_step.html
new file mode 100644
index 000000000..4d7512de1
--- /dev/null
+++ b/ishtar_common/templates/ishtar/import_step_by_step.html
@@ -0,0 +1,50 @@
+{% extends "base.html" %}
+{% load i18n inline_formset %}
+
+{% block content %}
+<h2>{% trans "Import step by step" %}</h2>
+<h3>{{import.name}} &ndash; {% trans "line " %} {{line_number_displayed}}</h3>
+{% if errors %}
+<div class="alert alert-danger" role="alert">
+ <p>{% trans "The following error(s) has been encountered while parsing the source file:" %}</p>
+ <ul>{% for error in errors %}
+ <li>{{error}}</li>
+ {% endfor %}</ul>
+</div>
+
+{% if values %}
+<table class="table table-striped">
+ {% for header, value in values %}
+ <tr>
+ <th>{{header}}</th>
+ <td>{{value}}</td>
+ </tr>
+ {% endfor %}
+</table>
+{% endif %}
+
+{% else %}
+
+<div>
+ <table class="table table-striped">
+ <tr>
+ <th>{% trans "Column number" %}</th>
+ <th>{% trans "Name" %}</th>
+ <th>{% trans "Raw CSV value" %}</th>
+ <th>{% trans "Current value" %}</th>
+ <th>{% trans "New value" %}</th>
+ {% comment %}<th>{% trans "Merged value" %}</th>{% endcomment %}
+ </tr>
+ {% for idx, name, raw, interpreted in values %}<tr>
+ <td>{{idx}}</td>
+ <td>{{name|safe}}</td>
+ <td>{{raw}}</td>
+ <td></td>
+ <td>{{interpreted}}</td>
+ </tr>{% endfor %}
+ </table>
+
+</div>
+{% endif %}
+{% endblock %}
+
diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py
index 5aa3bb409..1a7051b17 100644
--- a/ishtar_common/urls.py
+++ b/ishtar_common/urls.py
@@ -117,6 +117,8 @@ urlpatterns = [
views.ImportDeleteView.as_view(), name='import_delete'),
url(r'^import-link-unmatched/(?P<pk>[0-9]+)/$',
views.ImportLinkView.as_view(), name='import_link_unmatched'),
+ url(r'^import-step-by-step/(?P<pk>[0-9]+)/(?P<line_number>[0-9]+)/$',
+ views.ImportStepByStepView.as_view(), name='import_step_by_step'),
]
actions = []
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index 8b0d51601..1af2f58ae 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -1819,6 +1819,14 @@ class ImportListView(IshtarMixin, LoginRequiredMixin, ListView):
imprt.delayed_importation(request.session.session_key)
else:
imprt.importation()
+ elif action == 'IS':
+ if imprt.current_line is None:
+ imprt.current_line = imprt.skip_lines
+ imprt.save()
+ return HttpResponseRedirect(
+ reverse('import_step_by_step',
+ args=[imprt.pk, imprt.current_line + 1])
+ )
elif action == 'AC':
imprt.archive()
return HttpResponseRedirect(reverse(self.current_url))
@@ -1829,6 +1837,82 @@ class ImportListView(IshtarMixin, LoginRequiredMixin, ListView):
return dct
+class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
+ template_name = 'ishtar/import_step_by_step.html'
+ page_name = _(u"Import step by step")
+ current_url = 'import_step_by_step'
+
+ def get(self, request, *args, **kwargs):
+ try:
+ self.imprt_obj = models.Import.objects.get(
+ pk=int(self.kwargs['pk'])
+ )
+ except (models.Import.DoesNotExist, ValueError):
+ raise Http404
+ if not self.request.user.is_superuser:
+ # user can only edit his own imports
+ user = models.IshtarUser.objects.get(pk=self.request.user.pk)
+ if self.imprt_obj.user != user:
+ raise Http404
+ self.current_line_number = int(self.kwargs['line_number']) - 1
+ self.imprt = None
+ try:
+ self.imprt, data = self.imprt_obj.importation(
+ simulate=True,
+ line_to_process=self.current_line_number,
+ return_importer_and_data=True
+ )
+ except IOError as e:
+ self.errors = [e.message]
+ return
+ self.errors, self.new_data = None, None
+ if not data:
+ self.errors = self.imprt.errors
+ else:
+ self.new_data = data[:]
+ return super(ImportStepByStepView, self).get(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ dct = super(ImportStepByStepView, self).get_context_data(**kwargs)
+ dct['import'] = self.imprt_obj
+ dct['line_number_displayed'] = self.kwargs['line_number']
+ dct['errors'] = self.errors
+ if self.errors:
+ if self.imprt.current_csv_line:
+ headers = [f.label for f in self.imprt.get_formaters()]
+ dct['values'] = zip(headers, self.imprt.current_csv_line)
+ return dct
+ headers, interpreted_values = [], []
+ for formater in self.imprt.get_formaters():
+ lbl = formater.label
+ if formater.comment:
+ lbl += u' <i data-toggle="tooltip" class="fa '
+ lbl += u'fa-question-circle"'
+ lbl += u' aria-hidden="true" title="{}">'.format(
+ formater.comment.replace(u'"', u'&quot;'))
+ lbl += u'</i>'
+ headers.append(lbl)
+ field_name = formater.field_name[0]
+ if formater.export_field_name:
+ field_name = formater.export_field_name[0]
+ value = self.new_data[0].copy()
+ for key in field_name.split('__'):
+ if key in value:
+ value = value[key]
+ else:
+ value = u"-"
+ if value in (None, [], [None]):
+ value = _(u"* empty *")
+ if isinstance(value, list):
+ value = u" ; ".join(value)
+ interpreted_values.append(value)
+ dct['values'] = zip(
+ range(1, len(headers) + 1), headers, self.imprt.current_csv_line,
+ interpreted_values
+ )
+ return dct
+
+
class ImportListTableView(ImportListView):
template_name = 'ishtar/import_table.html'
current_url = 'current_imports_table'