Projet

Général

Profil

0001-WIP-import-users-from-csv-file-32833.patch

Paul Marillonnet, 07 mai 2019 17:02

Télécharger (9,11 ko)

Voir les différences:

Subject: [PATCH] WIP import users from csv file (#32833)

 src/authentic2/app_settings.py                |  1 +
 src/authentic2/data_transfer.py               | 33 +++++++++++++++
 src/authentic2/manager/forms.py               | 13 ++++++
 .../authentic2/manager/users_csv_import.html  | 34 +++++++++++++++
 src/authentic2/manager/urls.py                |  2 +
 src/authentic2/manager/user_views.py          | 41 ++++++++++++++++++-
 src/authentic2/manager/utils.py               | 11 +++++
 tests/users_csv_import_testfile.csv           |  3 ++
 8 files changed, 136 insertions(+), 2 deletions(-)
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/users_csv_import.html
 create mode 100644 tests/users_csv_import_testfile.csv
src/authentic2/app_settings.py
209 209
    A2_ACCOUNTS_URL=Setting(default=None, definition='IdP has no account page, redirect to this one.'),
210 210
    A2_CACHE_ENABLED=Setting(default=True, definition='Disable all cache decorators for testing purpose.'),
211 211
    A2_ACCEPT_EMAIL_AUTHENTICATION=Setting(default=True, definition='Enable authentication by email'),
212
    A2_USERS_CSV_IMPORT_APP_ID_LABEL=Setting(default='app_id', definition='Label of the applicative identifier used when importing users from csv'),
212 213

  
213 214
)
214 215

  
src/authentic2/data_transfer.py
1
import csv
2

  
3
from django.contrib.auth import get_user_model
1 4
from django.contrib.contenttypes.models import ContentType
5
from django.db import transaction
2 6

  
3 7
from django_rbac.models import Operation
4 8
from django_rbac.utils import (
5 9
    get_ou_model, get_role_model, get_role_parenting_model, get_permission_model)
6 10
from authentic2.a2_rbac.models import RoleAttribute
11
from authentic2 import app_settings
12
from authentic2.models import UserExternalId
7 13

  
8 14

  
9 15
def update_model(obj, d):
......
318 324
                    import_context.role_delete_orphans))
319 325

  
320 326
    return result
327

  
328

  
329
def import_users_from_csv(csv_file, ou):
330
    # TODO prevent illegal attribute-setting
331
    # TODO do not built the complete list from csv, may be memory-consuming
332
    # TODO detect the dialect
333
    # TODO CLI
334
    reader = csv.reader(csv_file) # XXX dialect
335
    rows = list(reader)
336
    app_id_label = app_settings.A2_USERS_CSV_IMPORT_APP_ID_LABEL
337

  
338
    try:
339
        rows[0].index(app_id_label)
340
    except ValueError:
341
        return -1, 'The csv file must contain an app_id column.'
342

  
343
    with transaction.atomic():
344
        User = get_user_model()
345
        for i in range(1,len(rows)):
346
            user_data = dict(zip(rows[0], rows[i]))
347
            app_id = user_data.pop(app_id_label)
348
            user = User.objects.create(**user_data)
349
            user.ou = ou
350
            user_external_id = UserExternalId.objects.create(
351
                user=user, external_id=app_id, source='import')
352

  
353
    return 0, 'Created %s users.' % (len(rows)-1)
src/authentic2/manager/forms.py
694 694

  
695 695
    class Meta:
696 696
        fields = ()
697

  
698

  
699
class UsersCsvImportForm(forms.Form):
700
    csv_file = forms.FileField(
701
        label=_('CSV file'),
702
        help_text=_('CSV file for the import.')
703
    )
704
    ou = forms.ChoiceField(
705
        label=_('Organizational unit'),
706
        help_text=_('The newly created users will be registered to the chosen OU.'),
707
        choices=utils.get_ou_choices(),
708
        initial=get_default_ou().uuid
709
    )
src/authentic2/manager/templates/authentic2/manager/users_csv_import.html
1
{% extends "authentic2/manager/form.html" %}
2
{% load i18n %}
3

  
4
{% block page-title %}
5
  {{ block.super }} - {{ view.title }}
6
{% endblock %}
7

  
8
{% block breadcrumb %}
9
  {{ block.super }}
10
  <a href="..">{% trans "Users" %}</a>
11
  <a href="">{{ view.title }}</a>
12
{% endblock %}
13

  
14
{% block content %}
15
  {% trans "Comma-separated value file with named columns:" %}
16
  <ul>
17
    <li>{{ app_id_label }}</li>
18
  {% for attr in attributes %}
19
    <li>{{ attr.name }}</li>
20
  {% endfor %}
21
  </ul>
22

  
23
  <form enctype="multipart/form-data" method="post">
24

  
25
    {% csrf_token %}
26
    {{ form.as_p }}
27
    {% if form.instance and form.instance.id %}
28
      <button class="submit-button">{% trans "Submit" %}</button>
29
      <button class="cancel-button" name="cancel" formnovalidate>{% trans "Cancel" %}</button>
30
    {% else %}
31
      <button class="submit-button">{% trans "Create" %}</button>
32
    {% endif %}
33
  </form>
34
{% endblock %}
src/authentic2/manager/urls.py
21 21
        url(r'^users/$', user_views.users, name='a2-manager-users'),
22 22
        url(r'^users/export/(?P<format>csv)/$',
23 23
            user_views.users_export, name='a2-manager-users-export'),
24
        url(r'^users/import/$',
25
            user_views.users_csv_import, name='a2-manager-users-import'),
24 26
        url(r'^users/add/$', user_views.user_add_default_ou,
25 27
            name='a2-manager-user-add-default-ou'),
26 28
        url(r'^users/(?P<ou_pk>\d+)/add/$', user_views.user_add,
src/authentic2/manager/user_views.py
15 15
from django.contrib import messages
16 16
from django.http import HttpResponseRedirect, QueryDict
17 17
from django.views.generic.detail import SingleObjectMixin
18
from django.views.generic import View
18
from django.views.generic import View, FormView
19 19

  
20 20
from import_export.fields import Field
21 21
import tablib
......
33 33
    BaseSubTableView, HideOUColumnMixin, BaseDeleteView, BaseDetailView
34 34
from .tables import UserTable, UserRolesTable, OuUserRolesTable
35 35
from .forms import (UserSearchForm, UserAddForm, UserEditForm,
36
    UserChangePasswordForm, ChooseUserRoleForm, UserRoleSearchForm, UserChangeEmailForm)
36
    UserChangePasswordForm, ChooseUserRoleForm, UserRoleSearchForm, UserChangeEmailForm,
37
    UsersCsvImportForm)
37 38
from .resources import UserResource
38 39
from .utils import get_ou_count
39 40
from . import app_settings
......
603 604

  
604 605

  
605 606
user_delete = UserDeleteView.as_view()
607

  
608

  
609
class UsersCsvImportView(FormView):
610
    form_class = UsersCsvImportForm
611
    template_name = 'authentic2/manager/users_csv_import.html'
612
    title = _('CSV import')
613
    success_url = '..'
614
    permissions = ['custom_user.add_user']
615

  
616
    def get_context_data(self, **kwargs):
617
        from authentic2.models import Attribute
618

  
619
        ctx = super(UsersCsvImportView, self).get_context_data()
620
        ctx.update({
621
            'attributes': Attribute.objects.all(),
622
            'app_id_label': a2_app_settings.A2_USERS_CSV_IMPORT_APP_ID_LABEL
623
        })
624
        return ctx
625

  
626
    def form_valid(self, form):
627
        from authentic2.data_transfer import import_users_from_csv
628

  
629
        ou_uuid = form.cleaned_data.get('ou')
630
        ou = get_ou_model().objects.get(uuid=ou_uuid) or get_default_ou()
631
        csv_file = form.cleaned_data.get('csv_file')
632

  
633
        if csv_file:
634
            status, message = import_users_from_csv(csv_file, ou)
635
        else:
636
            status, message = -1, 'Please provide a csv file.'
637

  
638
        # XXX django message
639
        return super(UsersCsvImportView, self).form_valid(form)
640

  
641

  
642
users_csv_import = UsersCsvImportView.as_view()
src/authentic2/manager/utils.py
26 26
@GlobalCache(timeout=10)
27 27
def get_ou_count():
28 28
    return get_ou_model().objects.count()
29

  
30

  
31
@GlobalCache(timeout=10)
32
def get_ou_choices():
33
    Ou = get_ou_model()
34
    return [(ou.uuid, ou.name) for ou in Ou.objects.all()]
35

  
36

  
37
def example_csv():
38
    # todo
39
    pass
tests/users_csv_import_testfile.csv
1
app_id,first_name,last_name
2
123,Hervé,Dupont
3
456,René,Durant
0
-