summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ishtar_common/static/media/styles.css4
-rw-r--r--ishtar_common/templates/ishtar/import_step_by_step.html215
-rw-r--r--ishtar_common/utils.py6
-rw-r--r--ishtar_common/views.py88
4 files changed, 285 insertions, 28 deletions
diff --git a/ishtar_common/static/media/styles.css b/ishtar_common/static/media/styles.css
index 9507674b7..6860ca93c 100644
--- a/ishtar_common/static/media/styles.css
+++ b/ishtar_common/static/media/styles.css
@@ -224,6 +224,10 @@ table.dataTable thead th, table.dataTable thead td {
overflow-x: auto;
}
+.step-by-step-csv textarea {
+ height: 50px;
+}
+
/* à adapter */
#message,
#message div{
diff --git a/ishtar_common/templates/ishtar/import_step_by_step.html b/ishtar_common/templates/ishtar/import_step_by_step.html
index c827156c5..293ce3dce 100644
--- a/ishtar_common/templates/ishtar/import_step_by_step.html
+++ b/ishtar_common/templates/ishtar/import_step_by_step.html
@@ -2,28 +2,109 @@
{% load i18n inline_formset link_to_window %}
{% block content %}
+
+{% comment %}
+<nav aria-label="Page navigation example">
+ <ul class="pagination justify-content-center">
+ <li class="page-item disabled">
+ <a class="page-link" href="#" tabindex="-1">Previous</a>
+ </li>
+ <li class="page-item"><a class="page-link" href="#">1</a></li>
+ <li class="page-item"><a class="page-link" href="#">2</a></li>
+ <li class="page-item"><a class="page-link" href="#">3</a></li>
+ <li class="page-item">
+ <a class="page-link" href="#">Next</a>
+ </li>
+ </ul>
+</nav>
+{% endcomment %}
+
+
<h2>{% trans "Import step by step" %} &ndash; {{import.name}} &ndash; {% trans "line " %} {{line_number_displayed}}</h2>
{% 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>
+ <table class="table table-striped">
+ <tr>
+ <th>{% trans "Column" %}</th>
+ <th>{% trans "Error" %}</th>
+ </tr>
+ {% for line, column, error in errors %}<tr>
+ <td>
+ {% if column %}
+ <a href="#col-{{column}}">
+ <span class="badge badge-info">
+ {% trans "Col."%} {{column}}
+ </span>
+ </a>
+ {% else %}
+ &ndash;
+ {% endif %}
+ </td>
+ <td>{{error}}</td>
+ </tr>{% endfor %}
+ </table>
</div>
{% if values %}
-<table class="table table-striped">
- {% for header, value in values %}
- <tr>
- <th>{{header}}</th>
- <td>{{value}}</td>
- </tr>
- {% endfor %}
-</table>
+<form action="." class='step-by-step-csv' method="post" name='step-by-step-csv'>
+ {% csrf_token %}
+ <table class="table table-striped">
+ <tr>
+ <th>{% trans "Column number" %}</th>
+ <th>{% trans "Name" %}</th>
+ <th>{% trans "Raw value" %}</th>
+ <th>{% trans "New value" %}</th>
+ </tr>
+ {% for idx, name, raw in values %}<tr id="col-{{idx}}">
+ <td>{{idx}}</td>
+ <td>{{name|safe}}</td>
+ <td>{{raw}}</td>
+ <td><textarea name="col-{{idx}}">{{raw}}</textarea></td>
+ </tr>{% endfor %}
+ </table>
+
+ <div id="footer">
+ <div id="validation-bar" class="row text-center">
+ <div class="col-sm">
+ <button type="submit" name="valid" id="btn-change-csv"
+ value="change-csv" class="btn btn-success">
+ {% trans "Update source file" %}
+ </button>
+ </div>
+ <div class="col-sm">
+ <a href="{% url 'import_step_by_step' import.pk line_number_displayed %}">
+ <button type="button" class="btn btn-secondary" id="btn-cancel">
+ {% trans "Cancel" %}
+ </button>
+ </a>
+ </div>
+ <div class="col-sm">
+ <a href="{% url 'current_imports' %}">
+ <button type="button" class="btn btn-secondary"
+ id="btn-back">
+ {% trans "Back to import list" %}
+ </button>
+ </a>
+ </div>
+ </div>
+ {% include 'ishtar/blocks/footer.html' %}
+ </div>
+</form>
{% endif %}
{% else %}
+{% if not have_change %}
+<div class="alert alert-success" role="alert">
+ <p>{% trans "No change for this item." %}</p>
+</div>
+{% else %}
+<div class="alert alert-warning" role="alert">
+ <p>{% trans "Changes will be made for this item" %}</p>
+</div>
+{% endif %}
+
{% if new_objects %}
<h3>{% trans "Objects to be created" %}</h3>
{% for path, cls, values in new_objects %}
@@ -39,7 +120,7 @@
</tr>
{% for k, value in values.items %}<tr>
<th>{{k|safe}}</th>
- <td>{{value}}</td>
+ <td>{{value|safe}}</td>
</tr>{% endfor %}
</table>
</div>
@@ -49,6 +130,7 @@
{% endif %}
{% if updated_objects %}
+
<h3>{% trans "Objects to be updated" %}</h3>
{% for path, obj, values, old_and_updated in updated_objects %}
@@ -70,7 +152,6 @@
</tr>{% endfor %}
</table>
-
<h5>{% trans "Updated values" %}</h5>
<table class="table table-striped">
<tr>
@@ -93,29 +174,127 @@
<hr/>
{% endfor %}
+{% endif %}
+
+{% if matched_objects %}
+<h3>{% trans "Objects matched with no changes" %}</h3>
+{% for path, obj, values in matched_objects %}
+
+<div class="card">
+ <div class="card-body">
+ <h5 class="card-title">{{path}} &ndash; {{obj}} {{obj|link_to_window}} ({{obj.get_verbose_name}})</h5>
+ </div>
+ <div class="card-body">
+
+ <h5>{% trans "Matching values" %}</h5>
+ <table class="table table-striped">
+ <tr>
+ <th>{% trans "Key" %}</th>
+ <th>{% trans "Value" %}</th>
+ </tr>
+ {% for k, value in values.items %}<tr>
+ <th>{{k|safe}}</th>
+ <td>{{value}}</td>
+ </tr>{% endfor %}
+ </table>
+ </div>
+</div>
+
+<hr/>
+{% endfor %}
{% endif %}
-<h3>{% trans "CSV values" %}</h3>
-<div>
+
+<h3>{% trans "Source file" %}</h3>
+
+<form action="." class='step-by-step-csv' method="post" name='step-by-step-csv'>
+ {% csrf_token %}
<table class="table table-striped">
<tr>
<th>{% trans "Column number" %}</th>
<th>{% trans "Name" %}</th>
- <th>{% trans "Raw CSV value" %}</th>
+ <th>{% trans "Raw value" %}</th>
<th>{% trans "Interpreted value" %}</th>
- {% comment %}<th>{% trans "Merged value" %}</th>{% endcomment %}
+ <th>{% trans "New value" %}</th>
</tr>
{% for idx, name, raw, interpreted in values %}<tr id="col-{{idx}}">
<td>{{idx}}</td>
<td>{{name|safe}}</td>
<td>{{raw}}</td>
<td>{{interpreted|safe}}</td>
+ <td><textarea name="col-{{idx}}">{{raw}}</textarea></td>
</tr>{% endfor %}
</table>
-</div>
+ <div id="footer">
+ <div id="validation-bar" class="row text-center">
+ <div class="col-sm">
+ <button type="submit" name="valid" id="btn-change-csv"
+ value="change-csv" class="btn btn-success"
+ disabled="disabled">
+ {% trans "Update source file" %}
+ </button>
+ </div>
+ <div class="col-sm">
+ <button type="submit" name="valid" id="btn-import"
+ value="import" class="btn btn-success">
+ {% trans "Import this line" %}
+ </button>
+ </div>
+ <div class="col-sm">
+ <a href="{% url 'import_step_by_step' import.pk line_number_displayed %}">
+ <button type="button" class="btn btn-secondary"
+ disabled="disabled" id="btn-cancel">
+ {% trans "Cancel" %}
+ </button>
+ </a>
+ </div>
+ <div class="col-sm">
+ <a href="{% url 'current_imports' %}">
+ <button type="button" class="btn btn-secondary"
+ id="btn-back">
+ {% trans "Back to import list" %}
+ </button>
+ </a>
+ </div>
+ </div>
+ {% include 'ishtar/blocks/footer.html' %}
+ </div>
+
+</form>
+<script type="text/javascript">
+ var modified = false;
+ var no_modif_msg = "{% trans "No modification made to the source file" %}";
+ var modif_msg = "{% trans "Modification to the source file have been made. Save or cancel theses modification before import." %}";
+
+ var no_modif_made = function(){
+ $("#btn-cancel").prop("title", no_modif_msg);
+ $("#btn-change-csv").prop("title", no_modif_msg);
+ };
+ var modif_made = function(){
+ if (modified) return;
+ modified = true;
+ $("#btn-cancel").prop("title", '');
+ $("#btn-cancel").prop("disabled", false);
+ $("#btn-change-csv").prop("title", '');
+ $("#btn-change-csv").prop("disabled", false);
+ $("#btn-import").prop("title", modif_msg);
+ $("#btn-import").prop("disabled", true);
+ $("#btn-back").prop("title", modif_msg);
+ $("#btn-back").prop("disabled", true);
+ };
+ $(function(){
+ $('textarea').bind('input propertychange', function() {
+ modif_made();
+ });
+ no_modif_made();
+ });
+</script>
{% endif %}
+
{% endblock %}
+{% block footer %}
+{% endblock %}
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 5a03f7f5f..c25cdf605 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -28,6 +28,7 @@ from django.conf import settings
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.sessions.backends.db import SessionStore
from django.core.cache import cache
+from django.core.exceptions import FieldDoesNotExist
from django.core.urlresolvers import reverse
from django.utils.datastructures import MultiValueDict as BaseMultiValueDict
from django.utils.safestring import mark_safe
@@ -406,7 +407,10 @@ def get_field_labels_from_path(model, path):
"""
labels = []
for key in path:
- field = model._meta.get_field(key)
+ try:
+ field = model._meta.get_field(key)
+ except:
+ return labels
if hasattr(field, 'verbose_name'):
labels.append(field.verbose_name)
if field.one_to_many or field.one_to_one or field.many_to_many:
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index 1fa968a95..d6d16568a 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -29,6 +29,7 @@ from tempfile import NamedTemporaryFile
from tidylib import tidy_document as tidy
import unicodedata
from unidecode import unidecode
+import unicodecsv
from weasyprint import HTML, CSS
from weasyprint.fonts import FontConfiguration
@@ -1844,7 +1845,7 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
page_name = _(u"Import step by step")
current_url = 'import_step_by_step'
- def get(self, request, *args, **kwargs):
+ def get_import(self):
try:
self.imprt_obj = models.Import.objects.get(
pk=int(self.kwargs['pk'])
@@ -1857,18 +1858,66 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
if self.imprt_obj.user != user:
raise Http404
self.current_line_number = int(self.kwargs['line_number']) - 1
- self.imprt = None
+
+ def update_csv(self, request):
+ prefix = 'col-'
+ submited_line = [(int(k[len(prefix):]), request.POST[k])
+ for k in request.POST if k.startswith(prefix)]
+ updated_line = [value for line, value in sorted(submited_line)]
+ filename = self.imprt_obj.imported_file.path
+ with open(filename, 'r') as f:
+ reader = unicodecsv.reader(
+ f, encoding=self.imprt_obj.encoding)
+ lines = []
+ for idx, line in enumerate(reader):
+ if idx == self.current_line_number:
+ line = updated_line
+ line = [v.encode(self.imprt_obj.encoding, errors='replace')
+ for v in line]
+ lines.append(line)
+ with open(filename, 'w') as f:
+ writer = csv.writer(f, **CSV_OPTIONS)
+ writer.writerows(lines)
+
+ def import_line(self, request, *args, **kwargs):
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
+ return super(ImportStepByStepView, self).get(request, *args,
+ **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ if not request.POST or request.POST.get('valid', None) not in (
+ 'change-csv', 'import'):
+ return self.get(request, *args, **kwargs)
+
+ self.get_import()
+ if request.POST.get('valid') == 'change-csv':
+ self.update_csv(request)
+ return self.get(request, *args, **kwargs)
+ self.import_line(request, *args, **kwargs)
+ # todo redirect to next
+ return self.get(request, *args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ self.get_import()
+ self.imprt = None
self.errors, self.new_data = None, None
- if not data:
+ 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 = [None, None, e.message]
+ return super(ImportStepByStepView, self).get(request, *args,
+ **kwargs)
+ if not data or not data[0]:
self.errors = self.imprt.errors
else:
self.new_data = data[:]
@@ -1882,7 +1931,10 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
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)
+ dct['values'] = zip(
+ range(1, len(headers) + 1), headers,
+ self.imprt.current_csv_line
+ )
return dct
headers, self.path_to_column, interpreted_values = [], {}, []
for idx, formater in enumerate(self.imprt.get_formaters()):
@@ -1934,7 +1986,12 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
continue
created_dict[k] = val
# check if it is not previously created
- key = (cls, tuple(sorted(created_dict.items())))
+ dct_key = []
+ for key, value in created_dict.items():
+ if isinstance(value, list):
+ value = tuple(sorted(value))
+ dct_key.append((key, value))
+ key = (cls, tuple(sorted(dct_key)))
if key in new_objects:
# regroup it
new_objects[key][0].append(label)
@@ -1954,10 +2011,11 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
]
updated_objects = []
+ main_changed = False
for path, obj, values, updated_values in self.imprt.updated_objects:
# transform path to explicit label
- label = self.transform_path_to_label(obj.__class__, path)
+ label = self.transform_path_to_label(self.imprt.OBJECT_CLS, path)
# transform key into explicit label
values = self.transform_keys_to_label(path, obj.__class__, values)
@@ -1979,6 +2037,7 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
changed = current_value != updated_value
current_value = self.get_value(current_value)
updated_value = self.get_value(updated_value)
+ main_changed |= changed
old_and_updated[k] = [current_value, updated_value,
changed]
@@ -1987,7 +2046,18 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView):
old_and_updated)
updated_objects.append((label, obj, values, old_and_updated))
- dct['updated_objects'] = updated_objects
+ dct['have_change'] = main_changed
+ dct['updated_objects'] = []
+ dct['matched_objects'] = []
+ for path, obj, values, old_and_updated in updated_objects:
+ if old_and_updated:
+ dct['updated_objects'].append(
+ (path, obj, values, old_and_updated)
+ )
+ else:
+ dct['matched_objects'].append(
+ (path, obj, values)
+ )
dct['ambiguous_objects'] = self.imprt.ambiguous_objects
dct['not_find_objects'] = self.imprt.not_find_objects
return dct