Index: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/accesscode.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/accesscode.py	(revision 9262)
+++ main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/accesscode.py	(revision 9263)
@@ -32,4 +32,5 @@
 from random import SystemRandom as random
 from zope.component import getUtility
+from zope.component.interfaces import IFactory
 from waeup.kofa.interfaces import IKofaUtils, IKofaPluggable, IObjectHistory
 from waeup.kofa.utils.helpers import now
@@ -152,10 +153,13 @@
     grok.implements(IAccessCodeBatch)
 
-    def __init__(self, creation_date, creator, batch_prefix, cost,
-                 entry_num, num=None):
+    def __init__(self, creation_date=None, creator=None, batch_prefix=None,
+                 cost=None, entry_num=None, num=None):
         super(AccessCodeBatch, self).__init__()
         self.creation_date = creation_date
         self.creator = creator
-        self.prefix = batch_prefix.upper()
+        try:
+            self.prefix = batch_prefix.upper()
+        except AttributeError:
+            self.prefix = None
         self.cost = cost
         self.entry_num = entry_num
@@ -310,4 +314,12 @@
         self[key] = batch
         self._p_changed = True
+        return
+
+    def addBatchByImport(self, batch, batch_id):
+        """Add an already created `batch` by import
+        with defined id.
+        """
+        self[batch_id] = batch
+        return
 
     def createBatch(self, creation_date, creator, prefix, cost,
@@ -457,4 +469,20 @@
         return
 
+class AccessCodeBatchFactory(grok.GlobalUtility):
+    """A factory for accesscodebatches.
+
+    We need this factory for the accesscodebatchprocessor.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.AccessCodeBatch')
+    title = u"Create a new accesscode batch.",
+    description = u"This factory instantiates new accesscode batch instances."
+
+    def __call__(self, *args, **kw):
+        return AccessCodeBatch(*args, **kw)
+
+    def getInterfaces(self):
+        return implementedBy(AccessCodeBatch)
+
 def get_access_code(access_code):
     """Get an access code instance.
Index: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py	(revision 9263)
+++ main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py	(revision 9263)
@@ -0,0 +1,91 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Batch processing components for accesscodes.
+
+"""
+import grok
+from zope.interface import Interface
+from waeup.kofa.interfaces import IBatchProcessor, IGNORE_MARKER
+from waeup.kofa.utils.batching import BatchProcessor
+from waeup.kofa.accesscodes.interfaces import IAccessCodeBatch
+
+class AccessCodeBatchProcessor(BatchProcessor):
+    """A batch processor for IAccessCodeBatch objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'accesscodebatchprocessor'
+    grok.name(util_name)
+
+    name = u'AccessCodeBatch Processor'
+    iface = IAccessCodeBatch
+
+    location_fields = ['batch_id',]
+    factory_name = 'waeup.AccessCodeBatch'
+
+    mode = None
+
+    def parentsExist(self, row, site):
+        return 'accesscodes' in site.keys()
+
+    def entryExists(self, row, site):
+        return row['batch_id'] in site['accesscodes'].keys()
+
+    def getParent(self, row, site):
+        return site['accesscodes']
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['batch_id'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addBatchByImport(obj, row['batch_id'])
+        return
+
+    def updateEntry(self, obj, row, site):
+        """Update obj to the values given in row.
+
+        Returns a string describing the fields changed.
+        """
+        changed = []
+        for key, value in row.items():
+            # Skip fields to be ignored.
+            if value == IGNORE_MARKER:
+                continue
+            if not hasattr(obj, key):
+                continue
+            try:
+                evalvalue = eval(value)
+                if isinstance(evalvalue, list):
+                    value = eval(value)
+            except:
+                pass
+            setattr(obj, key, value)
+            log_value = getattr(value, 'code', value)
+            changed.append('%s=%s' % (key, log_value))
+
+        # Log actions...
+        location_field = self.location_fields[0]
+        items_changed = ', '.join(changed)
+        grok.getSite()['accesscodes'].logger.info('%s - %s - Batch updated: %s'
+            % (self.name, row[location_field], items_changed))
+        return
Index: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py	(revision 9262)
+++ main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py	(revision 9263)
@@ -59,5 +59,5 @@
     """A factory for batches of access codes.
     """
-    creation_date = schema.Date(
+    creation_date = schema.Datetime(
         title = _(u'Creation date'),
         )
Index: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv	(revision 9263)
+++ main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv	(revision 9263)
@@ -0,0 +1,5 @@
+batch_id,cost,creation_date,creator,disabled_num,entry_num,num,prefix,used_num
+CLR-1,0.0,2012-04-28 07:28:48.719026,system,0,3,1,CLR,1
+HOS-2,0.0,2012-04-28 07:28:48.719026,system,0,0,2,HOS,0
+HOS-1,0.0,2012-09-19 08:59:27.341100,zope.manager,0,1000,1,HOS,1
+SFE-3,0.0,2012-04-28 07:28:48.719026,system,0,8,3,SFE,0
Index: main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py	(revision 9263)
+++ main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py	(revision 9263)
@@ -0,0 +1,103 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Tests for hostels and their UI components.
+"""
+import os
+import shutil
+import tempfile
+import grok
+import pytz
+from datetime import datetime, timedelta
+from zope.event import notify
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component.hooks import setSite, clearSite
+from zope.testbrowser.testing import Browser
+from zope.security.interfaces import Unauthorized
+from zope.catalog.interfaces import ICatalog
+from zope.component import queryUtility
+from waeup.kofa.app import University
+from waeup.kofa.interfaces import IObjectHistory, IKofaPluggable, IKofaUtils
+from waeup.kofa.testing import (
+    FunctionalLayer, FunctionalTestCase, setUp, tearDown, getRootFolder)
+from waeup.kofa.accesscodes.batching import AccessCodeBatchProcessor
+
+
+BATCH_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_batch_data.csv'),
+    'rb').read()
+
+BATCH_HEADER_FIELDS = BATCH_SAMPLE_DATA.split(
+    '\n')[0].split(',')
+
+class ACFullSetup(FunctionalTestCase):
+
+    def setUp(self):
+        super(ACFullSetup, self).setUp()
+
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        # we add the site immediately after creation to the
+        # ZODB. Catalogs and other local utilities are not setup
+        # before that step.
+        self.app = self.getRootFolder()['app']
+        # Set site here. Some of the following setup code might need
+        # to access grok.getSite() and should get our new app then
+        setSite(app)
+
+        # Put the prepopulated site into test ZODB and prepare test
+        # browser
+        self.browser = Browser()
+        self.browser.handleErrors = False
+
+        self.logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'accesscodes.log')
+
+    def tearDown(self):
+        super(ACFullSetup, self).tearDown()
+        clearSite()
+        shutil.rmtree(self.dc_root)
+
+class ACBatchProcessorTest(ACFullSetup):
+
+    layer = FunctionalLayer
+
+    def test_import(self):
+        self.processor = AccessCodeBatchProcessor()
+        self.workdir = tempfile.mkdtemp()
+        self.csv_file = os.path.join(self.workdir, 'sample_batch_data.csv')
+        open(self.csv_file, 'wb').write(BATCH_SAMPLE_DATA)
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, BATCH_HEADER_FIELDS)
+        self.assertEqual(num_warns,0)
+        self.assertEqual(len(self.app['accesscodes'].keys()), 7)
+        self.assertEqual(self.app['accesscodes']['CLR-1'].num,1)
+        logcontent = open(self.logfile).read()
+        self.assertTrue(
+            'system - AccessCodeBatch Processor - CLR-1 - '
+            'Batch updated: num=1, creator=system, entry_num=3, '
+            'creation_date=2012-04-28 07:28:48.719026+00:00, '
+            'prefix=CLR, cost=0.0, disabled_num=0'
+            in logcontent)
+        shutil.rmtree(os.path.dirname(fin_file))
+        return
