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 %} +
+ {% csrf_token %} + {{ form.as_p }} +

{% trans "Expected file format" %}: CSV

+ +

Example: +

+ "login", "first name", "last name", "expiration", "password"
+ "foo", "Foo", "User", "2015-12-31", "secret"
+ "bar", "User", "Bar", "2015-04-30", ""
+ "test", "", "", "", ""
+
+

+

+

+{% 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