diff --git a/apps/user_management/management/__init__.py b/apps/user_management/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/user_management/management/commands/__init__.py b/apps/user_management/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/user_management/management/commands/import_users.py b/apps/user_management/management/commands/import_users.py new file mode 100644 index 0000000000..b02d08d5b4 --- /dev/null +++ b/apps/user_management/management/commands/import_users.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import + +import csv, os, sys +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError, LabelCommand +from django.utils.simplejson import loads, dumps +from django.contrib.auth.models import User +from django.db.utils import IntegrityError + + +def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): + # csv.py doesn't do Unicode; encode temporarily as UTF-8: + csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), + dialect=dialect, **kwargs) + for row in csv_reader: + # decode UTF-8 back to Unicode, cell by cell: + yield [unicode(cell, 'utf-8') for cell in row] + +def utf_8_encoder(unicode_csv_data): + for line in unicode_csv_data: + yield line.encode('utf-8') + + +class Command(LabelCommand): + args = '' + help = 'Import users from a CSV file with the field order: username, firstname, lastname, email.' + option_list = LabelCommand.option_list + ( + make_option('--noinput', action='store_false', dest='interactive', + default=True, help='Do not ask the user for confirmation before ' + 'starting.'), + make_option('--password', action='store', dest='password', + help='The default password to assign to each new user.'), + make_option('--skip-repeated', action='store_true', dest='skip_repeated', + default=False, help='Don\'t exit if the user already exists.'), + ) + + def handle_label(self, label, **options): + if not os.access(label, os.R_OK): + raise CommandError("File '%s' is not readable." % label) + + if options['password']: + default_password = options['password'] + else: + default_password = None + + if _confirm(options['interactive']) == 'yes': + print 'Beginning import...' + with open(label, 'rb') as f: + reader = unicode_csv_reader(f) + try: + for row in reader: + print 'Adding: %s' % ', '.join(row) + try: + user = User( + username=row[0], + first_name=row[1], + last_name=row[2], + email=row[3] + ) + user.set_password(default_password) + user.save() + except IntegrityError: + print 'Repeated user entry: %s' % ', '.join(row) + if options['skip_repeated']: + print 'Ignoring.' + else: + sys.exit() + + except csv.Error, e: + sys.exit('file %s, line %d: %s' % (label, reader.line_num, e)) + else: + print 'Finish.' + else: + print 'Cancelled.' + + +def _confirm(interactive): + if not interactive: + return 'yes' + return raw_input('You have requested to import a number of users from a CSV file.\n' + 'Are you sure you want to do this?\n' + 'Type \'yes\' to continue, or any other value to cancel: ') diff --git a/apps/user_management/views.py b/apps/user_management/views.py index b9c9a6c9e8..85fea6a198 100644 --- a/apps/user_management/views.py +++ b/apps/user_management/views.py @@ -42,8 +42,11 @@ def user_list(request): { 'name': _(u'active'), 'attribute': encapsulate(lambda x: two_state_template(x.is_active)), - } - + }, + { + 'name': _(u'has usable password?'), + 'attribute': encapsulate(lambda x: two_state_template(x.has_usable_password())), + }, ], 'multi_select_as_buttons': True, }, @@ -82,7 +85,9 @@ def user_add(request): if request.method == 'POST': form = UserForm(request.POST) if form.is_valid(): - user = form.save() + user = form.save(commit=False) + user.set_unusable_password() + user.save() messages.success(request, _(u'User "%s" created successfully.') % user) return HttpResponseRedirect(reverse('user_set_password', args=[user.pk])) else: diff --git a/docs/releases/0.12.rst b/docs/releases/0.12.rst index 621145bdee..965e12617e 100644 --- a/docs/releases/0.12.rst +++ b/docs/releases/0.12.rst @@ -97,12 +97,31 @@ follow:: **Optional arguments** -* The ``--noinput`` argument skips confirmation and starts the upload inmediately. +* The ``--noinput`` argument skips confirmation and starts the upload immediately. * The ``--metadata`` argument allows specifing what metadata will be assigned to the documents when uploaded. * And the ``--document_type`` applies a previously defined document type to the uploaded documents. +Out of process user import +~~~~~~~~~~~~~~~~~~~~~~~~~~ +A management command has been added to import a large number users +from a CSV file. The command line options for this feature are as +follow:: + + $ ./manage.py import_users --noinput --password=welcome123 --skip-repeated user_list.csv + +The CSV field order must be: username, first name, last name and email, any other +column after those is ignored. + +**Optional arguments** + +* The ``--noinput`` argument skips confirmation and starts the import immediately. +* The ``--password`` argument allows specifing what default password will be assigned + to all the new users that are imported. +* The ``--skip-repeated`` tells the importedr to not stop when finding + that a user already exists in the database. + Upgrading from a previous version =================================