Projet

Général

Profil

0001-manager-display-progress-while-importing-users-50163.patch

Valentin Deniaud, 21 avril 2021 12:29

Télécharger (9,35 ko)

Voir les différences:

Subject: [PATCH] manager: display progress while importing users (#50163)

 src/authentic2/csv_import.py                  | 15 +++++---
 .../authentic2/manager/user_import.html       | 34 ++++++++++++-------
 .../manager/user_import_report_row.html       | 12 +++++++
 src/authentic2/manager/user_import.py         | 17 ++++++++--
 src/authentic2/manager/user_views.py          |  6 +++-
 tests/test_user_manager.py                    |  5 +++
 6 files changed, 70 insertions(+), 19 deletions(-)
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/user_import_report_row.html
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
-