Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py	(revision 9202)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py	(revision 9202)
@@ -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 hostels.
+
+"""
+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.hostels.interfaces import IHostel
+
+class HostelProcessor(BatchProcessor):
+    """A batch processor for IUserAccount objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'hostelprocessor'
+    grok.name(util_name)
+
+    name = u'Hostel Processor'
+    iface = IHostel
+
+    location_fields = ['hostel_id',]
+    factory_name = 'waeup.Hostel'
+
+    mode = None
+
+    def parentsExist(self, row, site):
+        return 'hostels' in site.keys()
+
+    def entryExists(self, row, site):
+        return row['hostel_id'] in site['hostels'].keys()
+
+    def getParent(self, row, site):
+        return site['hostels']
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['hostel_id'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addHostel(obj)
+        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()['hostels'].logger.info('%s - %s - Record updated: %s'
+            % (self.name, row[location_field], items_changed))
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/hostel.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/hostel.py	(revision 9201)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/hostel.py	(revision 9202)
@@ -22,4 +22,5 @@
 from zope.event import notify
 from zope.component import getUtility
+from zope.component.interfaces import IFactory
 from datetime import datetime
 from waeup.kofa.utils.helpers import attrs_to_fields
@@ -271,4 +272,21 @@
 Bed = attrs_to_fields(Bed)
 
+class HostelFactory(grok.GlobalUtility):
+    """A factory for hostels.
+
+    We need this factory for the hostel processor.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.Hostel')
+    title = u"Create a new hostel.",
+    description = u"This factory instantiates new hostel instances."
+
+    def __call__(self, *args, **kw):
+        return Hostel()
+
+    def getInterfaces(self):
+        return implementedBy(Hostel)
+
+
 @grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
 def handle_bedticket_removed(bedticket, event):
@@ -279,2 +297,3 @@
         bedticket.bed.owner = NOT_OCCUPIED
         notify(grok.ObjectModifiedEvent(bedticket.bed))
+
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_hostel_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_hostel_data.csv	(revision 9202)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_hostel_data.csv	(revision 9202)
@@ -0,0 +1,11 @@
+beds_for_all,beds_for_final,beds_for_fresh,beds_for_pre,beds_for_returning,beds_reserved,blocks_for_female,blocks_for_male,floors_per_block,hostel_id,hostel_name,rooms_per_floor,sort_id,special_handling
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['A'],[],1,block-a-upper-hostel,Block A Upper Hostel,32,20,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['B'],[],1,block-b-upper-hostel,Block B Upper Hostel,32,30,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['C'],[],1,block-c-upper-hostel,Block C Upper Hostel,32,40,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['D'],[],1,block-d-upper-hostel,Block D Upper Hostel,32,50,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['E'],[],1,block-e-down-hostel,Block E Down Hostel,32,60,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['F'],[],1,block-f-down-hostel,Block F Down Hostel,32,70,regular
+[],"['A', 'B']","['E', 'F']",['G'],"['C', 'D']",[],['G'],[],1,block-g-down-hostel,Block G Down Hostel,32,80,regular
+[],"['A', 'B']","['D', 'E']",['F'],['C'],[],[],"['A', 'B']",1,hall-a,Hall A,44,90,regular
+[],"['A', 'B']","['D', 'E']",['F'],['C'],[],[],"['C', 'D']",1,hall-b,Hall B,44,100,regular
+[],[],[],[],[],[],[],['A'],1,stores-32,Stores 32,16,10,regular
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py	(revision 9201)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py	(revision 9202)
@@ -37,4 +37,5 @@
 from waeup.kofa.hostels.container import HostelsContainer
 from waeup.kofa.hostels.hostel import Hostel, Bed
+from waeup.kofa.hostels.batching import HostelProcessor
 from waeup.kofa.hostels.export import BedExporter, HostelExporter
 from waeup.kofa.testing import (FunctionalLayer, FunctionalTestCase)
@@ -42,4 +43,11 @@
 from waeup.kofa.students.accommodation import BedTicket
 from waeup.kofa.university.department import Department
+
+HOSTEL_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_hostel_data.csv'),
+    'rb').read()
+
+HOSTEL_HEADER_FIELDS = HOSTEL_SAMPLE_DATA.split(
+    '\n')[0].split(',')
 
 class HostelsContainerTestCase(FunctionalTestCase):
@@ -158,4 +166,7 @@
         self.browser = Browser()
         self.browser.handleErrors = False
+
+        self.logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'hostels.log')
 
     def tearDown(self):
@@ -381,7 +392,5 @@
         self.assertEqual(len(results), 0)
         # Both actions have been logged.
-        logfile = os.path.join(
-            self.app['datacenter'].storage, 'logs', 'hostels.log')
-        logcontent = open(logfile).read()
+        logcontent = open(self.logfile).read()
         self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
                         ' - hall-1 - cleared' in logcontent)
@@ -424,2 +433,32 @@
             )
         return
+
+class HostelProcessorTest(HostelsFullSetup):
+
+    layer = FunctionalLayer
+
+    def test_import(self):
+        self.processor = HostelProcessor()
+        self.workdir = tempfile.mkdtemp()
+        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
+        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, HOSTEL_HEADER_FIELDS)
+        self.assertEqual(num_warns,0)
+        self.assertEqual(len(self.app['hostels'].keys()), 11) # including hall-x
+        self.assertEqual(self.app['hostels'][
+            'block-a-upper-hostel'].hostel_id,'block-a-upper-hostel')
+        self.assertEqual(self.app['hostels'][
+            'block-a-upper-hostel'].beds_for_final, ['A', 'B'])
+        logcontent = open(self.logfile).read()
+        self.assertTrue(
+            "Hostel Processor - block-a-upper-hostel - "
+            "Record updated: beds_for_pre=['G'], floors_per_block=1, "
+            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
+            "blocks_for_male=[], hostel_id=block-a-upper-hostel, "
+            "sort_id=20, beds_for_returning=['C', 'D'], "
+            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
+            "blocks_for_female=['A'], beds_for_all=[], beds_reserved=[]"
+            in logcontent)
+        shutil.rmtree(os.path.dirname(fin_file))
+        return
