0001-WIP-import-users-from-csv-file-32833.patch
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 |
- |