Index: main/waeup.kofa/trunk/src/waeup/kofa/authentication.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/authentication.py	(revision 8972)
+++ main/waeup.kofa/trunk/src/waeup/kofa/authentication.py	(revision 8973)
@@ -21,4 +21,5 @@
 from zope.event import notify
 from zope.component import getUtility, getUtilitiesFor
+from zope.component.interfaces import IFactory
 from zope.interface import Interface
 from zope.schema import getFields
@@ -35,5 +36,7 @@
 from waeup.kofa.interfaces import (ILocalRoleSetEvent,
     IUserAccount, IAuthPluginUtility, IPasswordValidator,
-    IKofaPrincipal, IKofaPrincipalInfo, IKofaPluggable)
+    IKofaPrincipal, IKofaPrincipalInfo, IKofaPluggable,
+    IBatchProcessor)
+from waeup.kofa.utils.batching import BatchProcessor
 
 def setup_authentication(pau):
@@ -363,4 +366,58 @@
                 obj, local_role, user_name, granted=False))
     return
+
+class UserAccountFactory(grok.GlobalUtility):
+    """A factory for user accounts.
+
+    This factory is only needed for imports.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.UserAccount')
+    title = u"Create a user.",
+    description = u"This factory instantiates new user account instances."
+
+    def __call__(self, *args, **kw):
+        return Account(name=None, password='')
+
+    def getInterfaces(self):
+        return implementedBy(Account)
+
+class UserProcessor(BatchProcessor):
+    """A batch processor for IUserAccount objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'userprocessor'
+    grok.name(util_name)
+
+    name = u'User Processor'
+    iface = IUserAccount
+
+    location_fields = ['name',]
+    factory_name = 'waeup.UserAccount'
+
+    mode = None
+
+    def parentsExist(self, row, site):
+        return 'users' in site.keys()
+
+    def entryExists(self, row, site):
+        return row['name'] in site['users'].keys()
+
+    def getParent(self, row, site):
+        return site['users']
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['name'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addAccount(obj)
+        return
+
 
 class UsersPlugin(grok.GlobalUtility):
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 8972)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 8973)
@@ -99,5 +99,7 @@
     'Student Payment Processor', 'Student Processor',
     'StudentStudyCourse Processor (update only)',
-    'StudentStudyLevel Processor', 'Verdict Processor (update only)']
+    'StudentStudyLevel Processor',
+    'User Processor',
+    'Verdict Processor (update only)']
 
     >>> importerselect.getControl('Faculty Processor').selected = True
@@ -420,4 +422,68 @@
     ...File:...mycertcourses_zope.mgr.csv...
 
+Batch processing users
+======================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for certificate courses:
+
+    >>> open('users.csv', 'wb').write(
+    ... """name,title,public_name,email,phone,local_roles
+    ... uli,Uli Fouquet,Chief Developer,uli@abc.de,+49-234-567
+    ... henrik, Henrik Bettermann,Admin,henrik@abc.de,+49-234-567,['waeup.PortalManager']
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload CSV file').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('users.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'users.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Batch processing').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'users_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('User Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...users_zope.mgr.csv...
 
 Pending files
@@ -551,6 +617,5 @@
     >>> sorted(os.listdir(dc_path + '/finished'))
     ['certificates_zope.mgr.create.finished.csv', ...,
-     'newfaculties_zope.mgr.create.finished.csv',
-     'newfaculties_zope.mgr.csv']
+    'users_zope.mgr.create.finished.csv', 'users_zope.mgr.csv']
 
 
