0001-manager-display-progress-while-importing-users-50163.patch
src/authentic2/csv_import.py | ||
---|---|---|
363 | 363 |
line_error = LineError.from_error(line_error) |
364 | 364 |
self.errors.append(line_error) |
365 | 365 | |
366 |
def run(self, fd_or_str, encoding, ou=None, simulate=False): |
|
366 |
def run(self, fd_or_str, encoding, ou=None, simulate=False, progress_callback=None):
|
|
367 | 367 |
self.ou = ou or get_default_ou() |
368 | 368 |
self.errors = [] |
369 | 369 |
self._missing_roles = set() |
... | ... | |
379 | 379 | |
380 | 380 |
try: |
381 | 381 |
with atomic(): |
382 |
for row in self.rows: |
|
382 |
for i, row in enumerate(self.rows): |
|
383 |
if progress_callback: |
|
384 |
progress_callback(_('importing'), i, len(self.rows)) |
|
383 | 385 |
try: |
384 | 386 |
if not self.do_import_row(row, unique_map): |
385 | 387 |
self.rows_with_errors += 1 |
... | ... | |
393 | 395 |
except Simulate: |
394 | 396 |
pass |
395 | 397 | |
396 |
for action in [parse_csv, self.parse_header_row, self.parse_rows, do_import]: |
|
398 |
def parse_rows(): |
|
399 |
self.parse_rows(progress_callback) |
|
400 | ||
401 |
for action in [parse_csv, self.parse_header_row, parse_rows, do_import]: |
|
397 | 402 |
action() |
398 | 403 |
if self.errors: |
399 | 404 |
break |
... | ... | |
514 | 519 |
except (AttributeError, TypeError, KeyError): |
515 | 520 |
self.add_error(LineError('unknown-flag', _('unknown flag "%s"'), line=1, column=column)) |
516 | 521 | |
517 |
def parse_rows(self): |
|
522 |
def parse_rows(self, progress_callback=None):
|
|
518 | 523 |
base_form_class = ImportUserForm |
519 | 524 |
if SOURCE_NAME in self.headers_by_name: |
520 | 525 |
base_form_class = ImportUserFormWithExternalId |
521 | 526 |
form_class = modelform_factory(User, fields=self.headers_by_name.keys(), form=base_form_class) |
522 | 527 |
rows = self.rows = [] |
523 | 528 |
for i, row in enumerate(self.csv_importer.rows[1:]): |
529 |
if progress_callback: |
|
530 |
progress_callback(_('parsing'), i, len(self.csv_importer.rows)) |
|
524 | 531 |
csv_row = self.parse_row(form_class, row, line=i + 2) |
525 | 532 |
self.has_errors = self.has_errors or not (csv_row.is_valid) |
526 | 533 |
rows.append(csv_row) |
src/authentic2/manager/templates/authentic2/manager/user_import.html | ||
---|---|---|
56 | 56 |
</thead> |
57 | 57 |
<tbody> |
58 | 58 |
{% for report in reports %} |
59 |
<tr data-uuid="{{ report.uuid }}"> |
|
60 |
<td class="created"> |
|
61 |
{% if report.state != report.STATE_RUNNING %} |
|
62 |
<a href="{% url "a2-manager-users-import-report" import_uuid=user_import.uuid report_uuid=report.uuid %}">{{ report.created }}</a> |
|
63 |
{% else %} |
|
64 |
{{ report.created }} |
|
65 |
{% endif %} |
|
66 |
</td> |
|
67 |
<td class="state">{{ report.state_display }}</td> |
|
68 |
<td class="applied">{% if not report.simulate %}<span class="icon-check"></span>{% endif %}</td> |
|
69 |
<td class="delete-action">{% if report.simulate %}<form method="post" id="delete-form-{{ report.uuid }}">{% csrf_token %}<button name="delete" value="{{ report.uuid }}">{% trans "Delete" %}</button></form>{% endif %}</td> |
|
70 |
</tr> |
|
59 |
<tr data-uuid="{{ report.uuid }}" data-url="{% url "a2-manager-users-import-report" import_uuid=user_import.uuid report_uuid=report.uuid %}"> |
|
60 |
{% include "authentic2/manager/user_import_report_row.html" %} |
|
61 |
</tr> |
|
71 | 62 |
{% endfor %} |
72 | 63 |
</tbody> |
73 | 64 |
</table> |
65 |
<script> |
|
66 |
function autorefresh(el) { |
|
67 |
$.ajax({ |
|
68 |
url: $(el).data('url'), |
|
69 |
success: function(html) { |
|
70 |
$(el).html(html); |
|
71 |
}, |
|
72 |
}); |
|
73 |
} |
|
74 | ||
75 |
function scheduleRefreshes() { |
|
76 |
$('tbody tr').each(function() { |
|
77 |
if($(this).children('td.state.running').length > 0) |
|
78 |
setInterval(autorefresh, 2000, this); |
|
79 |
}); |
|
80 |
} |
|
81 | ||
82 |
$(document).ready(scheduleRefreshes); |
|
83 |
</script> |
|
74 | 84 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/user_import_report_row.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 | ||
3 |
<td class="created"> |
|
4 |
{% if report.state != report.STATE_RUNNING %} |
|
5 |
<a href="{% url "a2-manager-users-import-report" import_uuid=user_import.uuid report_uuid=report.uuid %}">{{ report.created }}</a> |
|
6 |
{% else %} |
|
7 |
{{ report.created }} |
|
8 |
{% endif %} |
|
9 |
</td> |
|
10 |
<td class="state{% if report.state == report.STATE_RUNNING %} running{% endif %}">{{ report.state_display }}</td> |
|
11 |
<td class="applied">{% if not report.simulate %}<span class="icon-check"></span>{% endif %}</td> |
|
12 |
<td class="delete-action">{% if report.simulate %}<form method="post" id="delete-form-{{ report.uuid }}">{% csrf_token %}<button name="delete" value="{{ report.uuid }}">{% trans "Delete" %}</button></form>{% endif %}</td> |
src/authentic2/manager/user_import.py | ||
---|---|---|
166 | 166 |
@property |
167 | 167 |
def state_display(self): |
168 | 168 |
state = self.data['state'] |
169 |
return self.STATES.get(state, state) |
|
169 |
state_display = self.STATES.get(state, state) |
|
170 |
if state == self.STATE_RUNNING and 'progress' in self.data: |
|
171 |
state_display = '%s (%s)' % (state_display, self.data['progress']) |
|
172 |
return state_display |
|
170 | 173 | |
171 | 174 |
@property |
172 | 175 |
@contextlib.contextmanager |
... | ... | |
205 | 208 |
else: |
206 | 209 |
yield None |
207 | 210 | |
211 |
def callback(status, line, total): |
|
212 |
if total < 1 or not self.exists(): |
|
213 |
return |
|
214 |
with self.data_update as data: |
|
215 |
data['progress'] = '%s, %d%%' % (status, round((line / total) * 100)) |
|
216 | ||
208 | 217 |
def thread_worker(): |
209 | 218 |
from authentic2.csv_import import UserCsvImporter |
210 | 219 | |
... | ... | |
218 | 227 |
try: |
219 | 228 |
with publik_provisionning(): |
220 | 229 |
importer.run( |
221 |
fd, encoding=self.data['encoding'], ou=self.data['ou'], simulate=simulate |
|
230 |
fd, |
|
231 |
encoding=self.data['encoding'], |
|
232 |
ou=self.data['ou'], |
|
233 |
simulate=simulate, |
|
234 |
progress_callback=callback, |
|
222 | 235 |
) |
223 | 236 |
except Exception as e: |
224 | 237 |
logger.exception('error during report %s:%s run', self.user_import.uuid, self.uuid) |
src/authentic2/manager/user_views.py | ||
---|---|---|
902 | 902 |
form_class = UserEditImportForm |
903 | 903 |
permissions = ['custom_user.admin_user'] |
904 | 904 |
permissions_global = True |
905 |
template_name = 'authentic2/manager/user_import_report.html' |
|
906 | 905 | |
907 | 906 |
def dispatch(self, request, import_uuid, report_uuid): |
908 | 907 |
from authentic2.manager.user_import import UserImport |
... | ... | |
926 | 925 |
ctx['report_title'] = _('Execution') |
927 | 926 |
return ctx |
928 | 927 | |
928 |
def get_template_names(self): |
|
929 |
if self.request.is_ajax(): |
|
930 |
return ['authentic2/manager/user_import_report_row.html'] |
|
931 |
return ['authentic2/manager/user_import_report.html'] |
|
932 | ||
929 | 933 | |
930 | 934 |
user_import_report = UserImportReportView.as_view() |
931 | 935 |
tests/test_user_manager.py | ||
---|---|---|
500 | 500 | |
501 | 501 |
response = response.follow() |
502 | 502 | |
503 |
report_url = response.pyquery('tr[data-uuid="%s"]' % uuid).attr('data-url') |
|
504 |
ajax_resp = app.get(report_url, xhr=True) |
|
505 |
assert len(ajax_resp.pyquery('td')) == 4 |
|
506 |
assert 'body' not in ajax_resp |
|
507 | ||
503 | 508 |
def assert_timeout(duration, wait_function): |
504 | 509 |
start = time.time() |
505 | 510 |
while True: |
506 |
- |