diff options
| -rw-r--r-- | ishtar_common/data_importer.py | 45 | ||||
| -rw-r--r-- | ishtar_common/models_imports.py | 28 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/import_step_by_step.html | 50 | ||||
| -rw-r--r-- | ishtar_common/urls.py | 2 | ||||
| -rw-r--r-- | ishtar_common/views.py | 84 | 
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}} – {% 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'"')) +                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' | 
