diff --git a/src/authentic2/csv_import.py b/src/authentic2/csv_import.py
index 04c621472..56ece005c 100644
--- a/src/authentic2/csv_import.py
+++ b/src/authentic2/csv_import.py
@@ -363,7 +363,7 @@ class UserCsvImporter(object):
line_error = LineError.from_error(line_error)
self.errors.append(line_error)
- def run(self, fd_or_str, encoding, ou=None, simulate=False):
+ def run(self, fd_or_str, encoding, ou=None, simulate=False, progress_callback=None):
self.ou = ou or get_default_ou()
self.errors = []
self._missing_roles = set()
@@ -379,7 +379,9 @@ class UserCsvImporter(object):
try:
with atomic():
- for row in self.rows:
+ for i, row in enumerate(self.rows):
+ if progress_callback:
+ progress_callback(_('importing'), i, len(self.rows))
try:
if not self.do_import_row(row, unique_map):
self.rows_with_errors += 1
@@ -393,7 +395,10 @@ class UserCsvImporter(object):
except Simulate:
pass
- for action in [parse_csv, self.parse_header_row, self.parse_rows, do_import]:
+ def parse_rows():
+ self.parse_rows(progress_callback)
+
+ for action in [parse_csv, self.parse_header_row, parse_rows, do_import]:
action()
if self.errors:
break
@@ -514,13 +519,15 @@ class UserCsvImporter(object):
except (AttributeError, TypeError, KeyError):
self.add_error(LineError('unknown-flag', _('unknown flag "%s"'), line=1, column=column))
- def parse_rows(self):
+ def parse_rows(self, progress_callback=None):
base_form_class = ImportUserForm
if SOURCE_NAME in self.headers_by_name:
base_form_class = ImportUserFormWithExternalId
form_class = modelform_factory(User, fields=self.headers_by_name.keys(), form=base_form_class)
rows = self.rows = []
for i, row in enumerate(self.csv_importer.rows[1:]):
+ if progress_callback:
+ progress_callback(_('parsing'), i, len(self.csv_importer.rows))
csv_row = self.parse_row(form_class, row, line=i + 2)
self.has_errors = self.has_errors or not (csv_row.is_valid)
rows.append(csv_row)
diff --git a/src/authentic2/manager/templates/authentic2/manager/user_import.html b/src/authentic2/manager/templates/authentic2/manager/user_import.html
index 80b9b2d70..822c4c844 100644
--- a/src/authentic2/manager/templates/authentic2/manager/user_import.html
+++ b/src/authentic2/manager/templates/authentic2/manager/user_import.html
@@ -56,19 +56,29 @@
{% for report in reports %}
-
-
- {% if report.state != report.STATE_RUNNING %}
- {{ report.created }}
- {% else %}
- {{ report.created }}
- {% endif %}
- |
- {{ report.state_display }} |
- {% if not report.simulate %}{% endif %} |
- {% if report.simulate %}{% endif %} |
-
+
+ {% include "authentic2/manager/user_import_report_row.html" %}
+
{% endfor %}
+
{% endblock %}
diff --git a/src/authentic2/manager/templates/authentic2/manager/user_import_report_row.html b/src/authentic2/manager/templates/authentic2/manager/user_import_report_row.html
new file mode 100644
index 000000000..86f5653e8
--- /dev/null
+++ b/src/authentic2/manager/templates/authentic2/manager/user_import_report_row.html
@@ -0,0 +1,12 @@
+{% load i18n %}
+
+
+ {% if report.state != report.STATE_RUNNING %}
+ {{ report.created }}
+ {% else %}
+ {{ report.created }}
+ {% endif %}
+ |
+{{ report.state_display }} |
+{% if not report.simulate %}{% endif %} |
+{% if report.simulate %}{% endif %} |
diff --git a/src/authentic2/manager/user_import.py b/src/authentic2/manager/user_import.py
index a8d53636d..dba91e492 100644
--- a/src/authentic2/manager/user_import.py
+++ b/src/authentic2/manager/user_import.py
@@ -166,7 +166,10 @@ class Report(object):
@property
def state_display(self):
state = self.data['state']
- return self.STATES.get(state, state)
+ state_display = self.STATES.get(state, state)
+ if state == self.STATE_RUNNING and 'progress' in self.data:
+ state_display = '%s (%s)' % (state_display, self.data['progress'])
+ return state_display
@property
@contextlib.contextmanager
@@ -205,6 +208,12 @@ class Report(object):
else:
yield None
+ def callback(status, line, total):
+ if total < 1 or not self.exists():
+ return
+ with self.data_update as data:
+ data['progress'] = '%s, %d%%' % (status, round((line / total) * 100))
+
def thread_worker():
from authentic2.csv_import import UserCsvImporter
@@ -218,7 +227,11 @@ class Report(object):
try:
with publik_provisionning():
importer.run(
- fd, encoding=self.data['encoding'], ou=self.data['ou'], simulate=simulate
+ fd,
+ encoding=self.data['encoding'],
+ ou=self.data['ou'],
+ simulate=simulate,
+ progress_callback=callback,
)
except Exception as e:
logger.exception('error during report %s:%s run', self.user_import.uuid, self.uuid)
diff --git a/src/authentic2/manager/user_views.py b/src/authentic2/manager/user_views.py
index 35d6c7969..cf0ba4568 100644
--- a/src/authentic2/manager/user_views.py
+++ b/src/authentic2/manager/user_views.py
@@ -902,7 +902,6 @@ class UserImportReportView(MediaMixin, PermissionMixin, TemplateView):
form_class = UserEditImportForm
permissions = ['custom_user.admin_user']
permissions_global = True
- template_name = 'authentic2/manager/user_import_report.html'
def dispatch(self, request, import_uuid, report_uuid):
from authentic2.manager.user_import import UserImport
@@ -926,6 +925,11 @@ class UserImportReportView(MediaMixin, PermissionMixin, TemplateView):
ctx['report_title'] = _('Execution')
return ctx
+ def get_template_names(self):
+ if self.request.is_ajax():
+ return ['authentic2/manager/user_import_report_row.html']
+ return ['authentic2/manager/user_import_report.html']
+
user_import_report = UserImportReportView.as_view()
diff --git a/tests/test_user_manager.py b/tests/test_user_manager.py
index bcd28ce39..58783d26e 100644
--- a/tests/test_user_manager.py
+++ b/tests/test_user_manager.py
@@ -500,6 +500,11 @@ x,x,x,x'''.encode(
response = response.follow()
+ report_url = response.pyquery('tr[data-uuid="%s"]' % uuid).attr('data-url')
+ ajax_resp = app.get(report_url, xhr=True)
+ assert len(ajax_resp.pyquery('td')) == 4
+ assert 'body' not in ajax_resp
+
def assert_timeout(duration, wait_function):
start = time.time()
while True: