diff --git a/uauth/organization/forms.py b/uauth/organization/forms.py
index 90c13e2..6bfb5b2 100644
--- a/uauth/organization/forms.py
+++ b/uauth/organization/forms.py
@@ -27,3 +27,7 @@ class LocalAccountForm(forms.ModelForm):
class LocalAccountCreateForm(LocalAccountForm):
accounts_number = forms.IntegerField(_('Number of accounts to create'), required=False)
accounts_number_start = forms.IntegerField(_('First number of multiple accounts'), required=False)
+
+
+class UsersImportForm(forms.Form):
+ users_file = forms.FileField(_('Users file'))
diff --git a/uauth/organization/templates/organization/import_users.html b/uauth/organization/templates/organization/import_users.html
new file mode 100644
index 0000000..3e82fdd
--- /dev/null
+++ b/uauth/organization/templates/organization/import_users.html
@@ -0,0 +1,28 @@
+{% extends 'uauth/base.html' %}
+{% load i18n %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/uauth/organization/templates/organization/users.html b/uauth/organization/templates/organization/users.html
index cd0e065..f88a23d 100644
--- a/uauth/organization/templates/organization/users.html
+++ b/uauth/organization/templates/organization/users.html
@@ -7,6 +7,7 @@
{% block appbar %}
{% trans "Local users" %}
+{% trans "Import users" %}
{% trans "Create users" %}
{% endblock %}
diff --git a/uauth/organization/urls.py b/uauth/organization/urls.py
index 9697a1f..a169b26 100644
--- a/uauth/organization/urls.py
+++ b/uauth/organization/urls.py
@@ -6,6 +6,7 @@ urlpatterns = patterns('',
url(r'^$', manage, name='manage'),
url(r'^users/?$', users, name='manage-users'),
url(r'^users/create$', create_users, name='create-users'),
+ url(r'^users/import$', import_users, name='import-users'),
url(r'^users/(?P[\w]+)/$', view_user, name='view-user'),
url(r'^users/(?P[\w]+)/edit$', edit_user, name='edit-user'),
)
diff --git a/uauth/organization/utils.py b/uauth/organization/utils.py
index e59425c..f348c8d 100644
--- a/uauth/organization/utils.py
+++ b/uauth/organization/utils.py
@@ -18,3 +18,19 @@ def create_user(data):
return user
except:
return False
+
+def create_or_update_users(data):
+ created = 0
+ updated = 0
+ for user in data:
+ try:
+ account = LocalAccount.objects.get(username=user['username'])
+ if not user['password']:
+ del user['password']
+ account.__dict__.update(user)
+ account.save()
+ updated += 1
+ except LocalAccount.DoesNotExist:
+ if create_user(user):
+ created += 1
+ return created, updated
diff --git a/uauth/organization/views.py b/uauth/organization/views.py
index adae5e7..e125a4e 100644
--- a/uauth/organization/views.py
+++ b/uauth/organization/views.py
@@ -1,18 +1,21 @@
+import csv
+import datetime
+
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse_lazy
-from django.http import HttpResponseRedirect
+from django.shortcuts import render, redirect
from django.views.generic.base import TemplateView
from django.views.generic.list import ListView
from django.views.generic.edit import FormView, UpdateView
-from django.views.generic import DetailView
+from django.views.generic import DetailView, View
from django.contrib import messages
from django_tables2 import RequestConfig
-from .utils import create_user
+from .utils import create_user, create_or_update_users
from .models import LocalAccount, Organization
-from .forms import LocalAccountCreateForm, LocalAccountForm
+from .forms import LocalAccountCreateForm, LocalAccountForm, UsersImportForm
from .tables import AccountTable
@@ -96,9 +99,57 @@ class UserEditView(OrganizationMixin, UpdateView):
if 'delete' in self.request.POST:
self.object.delete()
messages.info(self.request, _('Account "%s" successfully deleted' % username))
- return HttpResponseRedirect(self.get_success_url())
+ return redirect(self.get_success_url())
else:
messages.info(self.request, _('Account "%s" successfully updated' % username))
return super(UserEditView, self).form_valid(form)
edit_user = UserEditView.as_view()
+
+
+class ImportUsersView(OrganizationMixin, TemplateView):
+ form_class = UsersImportForm
+ template_name = 'organization/import_users.html'
+
+ def get_context_data(self, **kwargs):
+ ctx = super(ImportUsersView, self).get_context_data(**kwargs)
+ ctx['form'] = self.form_class()
+ return ctx
+
+ def post(self, request, *args, **kwargs):
+ form = self.form_class(request.POST, request.FILES)
+ context = self.get_context_data(**kwargs)
+ context['form'] = form
+ if form.is_valid():
+ data = form.cleaned_data['users_file']
+ dialect = csv.Sniffer().sniff(data.read(1024))
+ data.seek(0)
+ reader = csv.reader(data, dialect)
+ reader.next()
+ users = []
+ for row in reader:
+ try:
+ user = {'username': row[0].strip(),
+ 'first_name': row[1].strip(),
+ 'last_name': row[2].strip(),
+ 'password': row[4].strip(),
+ 'organization': context['organization']
+ }
+ except IndexError:
+ # ignore wrong lines
+ continue
+ try:
+ user['expiration_date'] = datetime.datetime.strptime(row[3], '%Y-%m-%d')
+ except ValueError:
+ pass
+ users.append(user)
+ created, updated = create_or_update_users(users)
+ if created:
+ messages.info(request, _('%s accounts added' % created))
+ if updated:
+ messages.info(request, _('%s accounts updated' % updated))
+ return redirect(self.get_success_url())
+ else:
+ return self.render_to_response(context)
+
+import_users = ImportUsersView.as_view()
diff --git a/uauth/static/css/style.css b/uauth/static/css/style.css
index de28ea0..48a304f 100644
--- a/uauth/static/css/style.css
+++ b/uauth/static/css/style.css
@@ -61,4 +61,10 @@ ul.login li, #guest-login ul li, #voucher-login ul li, .loginbox li {
.icon-delete:before {
content: '\f1f8';
margin: 0 3px;
+}
+
+div.example {
+ background: #eee;
+ border: 1px solid #bbb;
+ font-family: Monospace;
}
\ No newline at end of file