Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/browser.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/browser.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/browser.py	(revision 9208)
@@ -1042,5 +1042,5 @@
         return
 
-    @action(_('Get login credentials'), style='primary')
+    @action(_('Send login credentials to email address'), style='primary')
     def register(self, **data):
         if not self.captcha_result.is_valid:
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/tests/test_browser.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/tests/test_browser.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/applicants/tests/test_browser.py	(revision 9208)
@@ -935,5 +935,5 @@
         self.browser.getControl(name="form.phone.area").value = '555'
         self.browser.getControl(name="form.phone.ext").value = '6666666'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertEqual(self.browser.url,
             self.container_path + '/registration_complete?email=xx%40yy.zz')
@@ -962,5 +962,5 @@
         self.browser.getControl(name="form.reg_number").value = 'anynumber'
         self.browser.getControl(name="form.email").value = 'xx@yy.zz'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue('No application record found.'
             in self.browser.contents)
@@ -972,5 +972,5 @@
         self.browser.getControl(name="form.reg_number").value = '1234'
         self.browser.getControl(name="form.email").value = 'xx@yy.zz'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue('An error occurred.' in self.browser.contents)
         # Let's set this attribute manually
@@ -981,5 +981,5 @@
         self.browser.getControl(name="form.reg_number").value = '1234'
         self.browser.getControl(name="form.email").value = 'xx@yy.zz'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         # Anonymous is not informed that firstname verification failed.
         # It seems that the record doesn't exist.
@@ -991,5 +991,5 @@
         self.browser.getControl(name="form.firstname").value = 'John'
         self.browser.getControl(name="form.reg_number").value = '1234'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue('Your password has already been set and used.'
             in self.browser.contents)
@@ -1005,5 +1005,5 @@
         self.browser.getControl(name="form.reg_number").value = '1234'
         self.browser.getControl(name="form.email").value = 'new@yy.zz'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         # Yeah, we succeded ...
         self.assertTrue('Your registration was successful.'
@@ -1021,5 +1021,5 @@
         self.browser.getControl(name="form.identifier").value = '1234'
         self.browser.getControl(name="form.email").value = 'aa@aa.ng'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue('No record found' in self.browser.contents)
         self.applicant.email = 'aa@aa.ng'
@@ -1029,5 +1029,5 @@
         self.browser.getControl(name="form.identifier").value = '1234'
         self.browser.getControl(name="form.email").value = 'aa@aa.ng'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue(
             'An email with your user name and password has been sent'
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/batchprocessing.txt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/batchprocessing.txt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/batchprocessing.txt	(revision 9208)
@@ -97,4 +97,5 @@
     'Course Processor', 'CourseTicket Processor',
     'Department Processor', 'Faculty Processor',
+    'Hostel Processor',
     'Student Payment Processor', 'Student Processor',
     'StudentStudyCourse Processor (update only)',
@@ -427,10 +428,10 @@
     >>> browser.open('http://localhost/app/datacenter')
 
-Prepare a CSV file for certificate courses:
+Prepare a CSV file for users:
 
     >>> open('users.csv', 'wb').write(
     ... """name,title,public_name,email,phone,roles
     ... uli,Uli Fouquet,Chief Developer,uli@abc.de,+49-234-567
-    ... henrik, Henrik Bettermann,Admin,henrik@abc.de,+49-234-567,['waeup.PortalManager']
+    ... henrik,Henrik Bettermann,Admin,henrik@abc.de,+49-234-567,['waeup.PortalManager']
     ... """)
 
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/browser.txt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/browser.txt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/browser.txt	(revision 9208)
@@ -81,5 +81,5 @@
   >>> browser.getControl(name="form.identifier").value = 'forgetful'
   >>> browser.getControl(name="form.email").value = 'aa@aa.ng'
-  >>> browser.getControl("Get login credentials").click()
+  >>> browser.getControl("Send login credentials").click()
   >>> print browser.contents
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/pages.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/pages.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/pages.py	(revision 9208)
@@ -2218,5 +2218,5 @@
         return None
 
-    @action(_('Get login credentials'), style='primary')
+    @action(_('Send login credentials to email address'), style='primary')
     def request(self, **data):
         if not self.captcha_result.is_valid:
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/static/jquery.dataTables.min.js
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/static/jquery.dataTables.min.js	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/static/jquery.dataTables.min.js	(revision 9208)
@@ -15,4 +15,7 @@
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * Customized by Henrik Bettermann: this.aLengthMenu, this._iDisplayLength, this._iDisplayEnd
+ *
  */
 (function(j,ra,p){j.fn.dataTableSettings=[];var D=j.fn.dataTableSettings;j.fn.dataTableExt={};var n=j.fn.dataTableExt;n.sVersion="1.7.6";n.sErrMode="alert";n.iApiIndex=0;n.oApi={};n.afnFiltering=[];n.aoFeatures=[];n.ofnSearch={};n.afnSortData=[];n.oStdClasses={sPagePrevEnabled:"paginate_enabled_previous",sPagePrevDisabled:"paginate_disabled_previous",sPageNextEnabled:"paginate_enabled_next",sPageNextDisabled:"paginate_disabled_next",sPageJUINext:"",sPageJUIPrev:"",sPageButton:"paginate_button",sPageButtonActive:"paginate_active",
@@ -38,5 +41,5 @@
 [];this.aoColumns=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false,bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=false;this.aoOpenRows=
 [];this.sDom="lfrtip";this.sPaginationType="two_button";this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.bAjaxDataGet=true;this.fnServerData=function(a,b,c){j.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(d,f){f=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})};
-this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d<b;d++){if(d%3===0&&d!==0)c=","+c;c=a[b-d-1]+c}}return c};this.aLengthMenu=[10,25,50,100,1000,5000];this.bDrawing=this.iDraw=0;this.iDrawError=-1;this._iDisplayLength=10;this._iDisplayStart=0;this._iDisplayEnd=10;this._iRecordsDisplay=this._iRecordsTotal=0;this.bJUI=false;this.oClasses=n.oStdClasses;this.bSorted=this.bFiltered=false;this.oInit=null}function r(a){return function(){var b=[A(this[n.iApiIndex])].concat(Array.prototype.slice.call(arguments));
+this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d<b;d++){if(d%3===0&&d!==0)c=","+c;c=a[b-d-1]+c}}return c};this.aLengthMenu=[10,25,50,100,1000,5000];this.bDrawing=this.iDraw=0;this.iDrawError=-1;this._iDisplayLength=1000;this._iDisplayStart=0;this._iDisplayEnd=1000;this._iRecordsDisplay=this._iRecordsTotal=0;this.bJUI=false;this.oClasses=n.oStdClasses;this.bSorted=this.bFiltered=false;this.oInit=null}function r(a){return function(){var b=[A(this[n.iApiIndex])].concat(Array.prototype.slice.call(arguments));
 return n.oApi[a].apply(this,b)}}function s(a){var b,c;if(a.bInitialised===false)setTimeout(function(){s(a)},200);else{sa(a);U(a);K(a,true);a.oFeatures.bAutoWidth&&$(a);b=0;for(c=a.aoColumns.length;b<c;b++)if(a.aoColumns[b].sWidth!==null)a.aoColumns[b].nTh.style.width=v(a.aoColumns[b].sWidth);if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}if(a.sAjaxSource!==null&&!a.oFeatures.bServerSide)a.fnServerData.call(a.oInstance,a.sAjaxSource,[],function(d){for(b=0;b<d.aaData.length;b++)u(a,
 d.aaData[b]);a.iInitDisplayStart=a._iDisplayStart;if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}K(a,false);w(a,d)});else if(!a.oFeatures.bServerSide){K(a,false);w(a)}}}function w(a,b){a._bInitComplete=true;if(typeof a.fnInitComplete=="function")typeof b!="undefined"?a.fnInitComplete.call(a.oInstance,a,b):a.fnInitComplete.call(a.oInstance,a)}function y(a,b,c){o(a.oLanguage,b,"sProcessing");o(a.oLanguage,b,"sLengthMenu");o(a.oLanguage,b,"sEmptyTable");o(a.oLanguage,
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterfinishedpage.pt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterfinishedpage.pt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterfinishedpage.pt	(revision 9208)
@@ -3,5 +3,5 @@
     <tr>
       <th i18n:translate="">File</th>
-      <th i18n:translate="">Size</th>
+      <th i18n:translate="">Datasets</th>
       <th i18n:translate="">Upload Date</th>
     </tr>
@@ -11,5 +11,5 @@
       <td><a tal:attributes="href python: 'download?filename=' + file.rel_path"
              tal:content="file/name">FILENAME</a></td>
-      <td tal:content="file/size">12 k</td>
+      <td tal:content="python: file.lines-1">12 k</td>
       <td tal:content="file/uploaddate">Mar 12, 2008</td>
     </tr>
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterpage.pt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterpage.pt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/browser/templates/datacenterpage.pt	(revision 9208)
@@ -16,5 +16,5 @@
           <th></th>
           <th i18n:translate="">File</th>
-          <th i18n:translate="">Size</th>
+          <th i18n:translate="">Datasets</th>
           <th i18n:translate="">Upload Date</th>
         </tr>
@@ -28,5 +28,5 @@
           <td><a tal:attributes="href python: 'download?filename=' + file.name"
                  tal:content="file/name">FILENAME</a></td>
-          <td tal:content="file/size">12 k</td>
+          <td tal:content="python: file.lines - 1">100</td>
           <td tal:content="file/uploaddate">Mar 12, 2008</td>
         </tr>
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/batching.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/batching.py	(revision 9208)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/batching.py	(revision 9208)
@@ -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/branches/uli-async-update/src/waeup/kofa/hostels/browser.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser.py	(revision 9208)
@@ -93,5 +93,5 @@
 
     def title(self):
-        co = self.context.getBedCoordinates()
+        co = self.context.coordinates
         return _('Block ${a}, Room ${b}, Bed ${c}',
             mapping = {'a':co[1], 'b':co[2], 'c':co[3]})
@@ -130,5 +130,7 @@
     form_fields = grok.AutoFields(IHostelsContainer)
     taboneactions = [_('Save')]
-    tabtwoactions = [_('Add hostel'), _('Remove selected')]
+    tabtwoactions = [_('Add hostel'),
+        _('Clear all hostels'),
+        _('Remove selected')]
 
     def update(self):
@@ -164,4 +166,12 @@
         return
 
+    @jsaction(_('Clear all hostels'))
+    def clearHostels(self, **data):
+        self.context.clearAllHostels()
+        self.flash(_('All hostels cleared.'))
+        write_log_message(self, 'all hostels cleared')
+        self.redirect(self.url(self.context, '@@manage')+'?tab2')
+        return
+
     @action(_('Save'), style='primary')
     def save(self, **data):
@@ -232,5 +242,6 @@
     tabtwoactions = [_('Update all beds'),
         _('Switch reservation of selected beds'),
-        _('Release selected beds')]
+        _('Release selected beds'),
+        _('Clear hostel')]
     not_occupied = NOT_OCCUPIED
 
@@ -243,4 +254,5 @@
         tabs.need()
         datatable.need()
+        warning.need()
         self.tab1 = self.tab2 = ''
         qs = self.request.get('QUERY_STRING', '')
@@ -327,4 +339,12 @@
         return
 
+    @jsaction(_('Clear hostel'))
+    def clearHostel(self, **data):
+        self.context.clearHostel()
+        self.flash(_('Hostel cleared.'))
+        write_log_message(self, 'cleared')
+        self.redirect(self.url(self.context, '@@manage')+'?tab2')
+        return
+
 class BedManageFormPage(KofaEditFormPage):
     """ View to edit bed data
@@ -334,5 +354,5 @@
     grok.require('waeup.manageHostels')
     form_fields = grok.AutoFields(IBedAllocateStudent).omit(
-        'bed_id').omit('bed_number').omit('bed_type')
+        'bed_id', 'bed_number', 'bed_type')
     label = _('Allocate student')
     pnav = 5
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser_templates/containerpage.pt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser_templates/containerpage.pt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser_templates/containerpage.pt	(revision 9208)
@@ -21,12 +21,18 @@
       <th i18n:translate="">Id</th>
       <th i18n:translate="">Name</th>
+      <th i18n:translate="">Booked Beds</th>
     </tr>
   </thead>
   <tbody>
-    <tr tal:repeat="value context/values">
-      <td> <a tal:attributes="href value/__name__">
-          <span tal:content="value/hostel_id">ID</span></a></td>
-      <td tal:content="value/hostel_name">NAME</td>
-    </tr>
+    <tal:tbody repeat="value context/values">
+      <tr tal:define = "stats value/bed_statistics">
+        <td> <a tal:attributes="href value/__name__">
+            <span tal:content="value/hostel_id">ID</span></a></td>
+        <td tal:content="value/hostel_name">NAME</td>
+        <td>
+          <span tal:replace="stats/booked">10</span> of <span tal:replace="stats/total">100</span>
+        </td>
+      </tr>
+    </tal:tbody>
   </tbody>
 </table>
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/container.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/container.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/container.py	(revision 9208)
@@ -51,4 +51,11 @@
         return
 
+    def clearAllHostels(self):
+        """Clear all hostels.
+        """
+        for hostel in self.values():
+            hostel.clearHostel()
+        return
+
     logger_name = 'waeup.kofa.${sitename}.hostels'
     logger_filename = 'hostels.log'
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/export.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/export.py	(revision 9208)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/export.py	(revision 9208)
@@ -0,0 +1,75 @@
+## $Id$
+##
+## Copyright (C) 2012 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
+##
+"""Exporters for hostels and beds.
+"""
+import grok
+from zope.securitypolicy.interfaces import IPrincipalRoleMap
+from waeup.kofa.interfaces import ICSVExporter
+from waeup.kofa.interfaces import MessageFactory as _
+from waeup.kofa.utils.batching import ExporterBase
+from waeup.kofa.utils.helpers import iface_names
+from waeup.kofa.hostels.interfaces import IBed, IHostel
+
+class HostelExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for hostels.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('hostels')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IHostel))) 
+
+    #: The title under which this exporter will be displayed
+    title = _(u'Hostels')
+
+    def export_all(self, site, filepath=None):
+        """Export hostels into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        hostels = site.get('hostels', {})
+        for hostel in hostels.values():
+            self.write_item(hostel, writer)
+        return self.close_outfile(filepath, outfile)
+
+class BedExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for courses.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('beds')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IBed))) + (
+        'hall', 'block', 'room', 'bed', 'special_handling', 'sex', 'bt')
+
+    #: The title under which this exporter will be displayed
+    title = _(u'Beds')
+
+    def export_all(self, site, filepath=None):
+        """Export beds into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        hostels = site.get('hostels', {})
+        for hostel in hostels.values():
+            for bed in hostel.values():
+                self.write_item(bed, writer)
+        return self.close_outfile(filepath, outfile)
+
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/hostel.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/hostel.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/hostel.py	(revision 9208)
@@ -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
@@ -40,4 +41,21 @@
         target = self.__name__
         return grok.getSite()['hostels'].logger_info(ob_class,target,comment)
+
+    @property
+    def bed_statistics(self):
+        total = len(self.keys())
+        booked = 0
+        for value in self.values():
+            if value.owner != NOT_OCCUPIED:
+                booked += 1
+        return {'booked':booked, 'total':total}
+
+    def clearHostel(self):
+        """Remove all beds
+        """
+        keys = [i for i in self.keys()] # create deep copy
+        for bed in keys:
+            del self[bed]
+        return
 
     def addBed(self, bed):
@@ -128,8 +146,41 @@
     grok.provides(IBed)
 
-    def getBedCoordinates(self):
+    @property
+    def coordinates(self):
         """Determine the coordinates from the bed_id.
         """
         return self.bed_id.split('_')
+
+    # The following property attributes are only needed
+    # for the exporter to ease evaluation with  Excel.
+
+    @property
+    def hall(self):
+        return self.coordinates[0]
+
+    @property
+    def block(self):
+        return self.coordinates[1]
+
+    @property
+    def room(self):
+        return self.coordinates[2]
+
+    @property
+    def bed(self):
+        return self.coordinates[3]
+
+    @property
+    def special_handling(self):
+        return self.bed_type.split('_')[0]
+
+    @property
+    def sex(self):
+        return self.bed_type.split('_')[1]
+
+    @property
+    def bt(self):
+        return self.bed_type.split('_')[2]
+
 
     def bookBed(self, student_id):
@@ -145,5 +196,5 @@
         """
         sh, sex, bt = self.bed_type.split('_')
-        hostel_id, block, room_nr, bed = self.getBedCoordinates()
+        hostel_id, block, room_nr, bed = self.coordinates
         hostel = self.__parent__
         beds_for_fresh = getattr(hostel,'beds_for_fresh',[])
@@ -221,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):
@@ -229,2 +297,3 @@
         bedticket.bed.owner = NOT_OCCUPIED
         notify(grok.ObjectModifiedEvent(bedticket.bed))
+
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/interfaces.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/interfaces.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/interfaces.py	(revision 9208)
@@ -17,5 +17,5 @@
 ##
 from datetime import datetime
-from zope.interface import invariant, Invalid
+from zope.interface import invariant, Invalid, Attribute
 from zope import schema
 from waeup.kofa.interfaces import (
@@ -58,4 +58,8 @@
         )
 
+    def clearAllHostels():
+        """Clear all hostels.
+        """
+
 class IHostel(IKofaObject):
     """A base representation of hostels.
@@ -63,6 +67,12 @@
     """
 
+    bed_statistics = Attribute('Number of booked and total beds')
+
     def loggerInfo(ob_class, comment):
         """Adds an INFO message to the log file
+        """
+
+    def clearHostel():
+        """Remove all beds.
         """
 
@@ -189,11 +199,10 @@
     """
 
+    coordinates = Attribute('The coordinates of the bed from bed_id')
+
     def loggerInfo(ob_class, comment):
         """Adds an INFO message to the log file
         """
 
-    def getBedCoordinates():
-        """Determine the coordinates from bed_id.
-        """
     def bookBed(student_id):
         """Book a bed for a student.
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/sample_hostel_data.csv
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/sample_hostel_data.csv	(revision 9208)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/sample_hostel_data.csv	(revision 9208)
@@ -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/branches/uli-async-update/src/waeup/kofa/hostels/tests.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/tests.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/tests.py	(revision 9208)
@@ -19,4 +19,5 @@
 Tests for hostels and their UI components.
 """
+import os
 import shutil
 import tempfile
@@ -36,8 +37,17 @@
 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)
 from waeup.kofa.students.student import Student
 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):
@@ -142,7 +152,7 @@
         # Create a bed
         bed = Bed()
-        bed.bed_id = u'xyz'
+        bed.bed_id = u'hall_block_room_bed'
         bed.bed_number = 1
-        bed.bed_type = u'abc'
+        bed.bed_type = u'a_b_c'
         self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
 
@@ -157,4 +167,7 @@
         self.browser.handleErrors = False
 
+        self.logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'hostels.log')
+
     def tearDown(self):
         super(HostelsFullSetup, self).tearDown()
@@ -174,12 +187,12 @@
         # We can find a certain bed
         cat = queryUtility(ICatalog, name='beds_catalog')
-        results = cat.searchResults(bed_type=(u'abc', u'abc'))
+        results = cat.searchResults(bed_type=(u'a_b_c', u'a_b_c'))
         results = [x for x in results] # Turn results generator into list
         assert len(results) == 1
-        assert results[0] is self.app['hostels']['hall-x']['xyz']
+        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
 
     def test_search_by_owner(self):
         # We can find a certain bed
-        myobj = self.app['hostels']['hall-x']['xyz']
+        myobj = self.app['hostels']['hall-x']['hall_block_room_bed']
         myobj.owner = u'abc'
         notify(grok.ObjectModifiedEvent(myobj))
@@ -188,5 +201,5 @@
         results = [x for x in results] # Turn results generator into list
         assert len(results) == 1
-        assert results[0] is self.app['hostels']['hall-x']['xyz']
+        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
 
 class HostelsUITests(HostelsFullSetup):
@@ -300,5 +313,5 @@
         self.assertMatches(
           '...No allocated bed selected...', self.browser.contents)
-        # Managers can manually allocate studenst after cancellation
+        # Managers can manually allocate students after cancellation
         self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
         self.browser.getControl(name="form.owner").value = [self.student_id]
@@ -334,4 +347,7 @@
         # Also the number of the bed has changed.
         self.assertFalse(new_number == old_number)
+        # The number of occupied bed are displayed on container page.
+        self.browser.open(self.container_path)
+        self.assertTrue('1 of 8' in self.browser.contents)
         # Remove entire hostel
         self.browser.open(self.manage_container_path)
@@ -346,2 +362,103 @@
         results = [x for x in results]
         assert len(results) == 0
+
+    def test_clear_hostels(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.container_path)
+        self.browser.getLink("Manage accommodation").click()
+        self.browser.getControl("Add hostel").click()
+        self.browser.getControl("Create hostel").click()
+        hall = self.app['hostels']['hall-1']
+        hall.blocks_for_female = ['A','B']
+        hall.rooms_per_floor = 1
+        hall.beds_for_fresh = ['A']
+        hall.beds_for_returning = ['B']
+        hall.beds_for_final = ['C']
+        hall.beds_for_all = ['D','E']
+        self.browser.open(self.container_path + '/hall-1/manage')
+        self.browser.getControl("Update all beds").click()
+        cat = queryUtility(ICatalog, name='beds_catalog')
+        results = cat.searchResults(bed_type=(None, None))
+        self.assertEqual(len(results), 11)
+        self.browser.getControl("Clear hostel").click()
+        self.assertEqual(len(self.app['hostels']['hall-1']), 0)
+        # Only the bed in hall-x remains in the catalog.
+        results = cat.searchResults(bed_type=(None, None))
+        self.assertEqual(len(results), 1)
+        # We can clear all hostels at the same time.
+        self.browser.open(self.manage_container_path)
+        self.browser.getControl("Clear all hostels").click()
+        results = cat.searchResults(bed_type=(None, None))
+        self.assertEqual(len(results), 0)
+        # Both actions have been logged.
+        logcontent = open(self.logfile).read()
+        self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
+                        ' - hall-1 - cleared' in logcontent)
+        self.assertTrue('zope.mgr - hostels.browser.HostelsContainerManagePage'
+                        ' - hostels - all hostels cleared' in logcontent)
+
+class ExportTests(HostelsFullSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ExportTests, self).setUp()
+        self.workdir = tempfile.mkdtemp()
+        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
+        return
+
+    def test_export_hostels(self):
+        exporter = HostelExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertEqual(
+            result,
+            '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\r\n,,,,,[],,,1,'
+            'hall-x,Hall 1,2,10,regular\r\n'
+            )
+        return
+
+    def test_export_beds(self):
+        exporter = BedExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertEqual(
+            result,
+            'bed_id,bed_number,bed_type,owner,hall,block,room,bed,'
+            'special_handling,sex,bt\r\nhall_block_room_bed,1,a_b_c,,'
+            'hall,block,room,bed,a,b,c\r\n'
+            )
+        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
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/permissions.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/permissions.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/permissions.py	(revision 9208)
@@ -213,4 +213,5 @@
                      'waeup.uploadStudentFile', 'waeup.showStudents',
                      'waeup.viewStudentsContainer','waeup.viewStudentsTab',
+                     'waeup.handleAccommodation',
                      'waeup.viewHostels', 'waeup.manageHostels',
                      'waeup.editUser'
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser.py	(revision 9208)
@@ -361,4 +361,26 @@
         return
 
+class ExportPDFAdmissionSlipPage(UtilityView, grok.View):
+    """Deliver a PDF Admission slip.
+    """
+    grok.context(IStudent)
+    grok.name('admission_slip.pdf')
+    grok.require('waeup.viewStudent')
+    prefix = 'form'
+
+    form_fields = grok.AutoFields(IStudentBase).select('student_id', 'reg_number')
+
+    @property
+    def label(self):
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        return translate(_('Admission Letter of'),
+            'waeup.kofa', target_language=portal_language) \
+            + ' %s' % self.context.display_fullname
+
+    def render(self):
+        students_utils = getUtility(IStudentsUtils)
+        return students_utils.renderPDFAdmissionLetter(self,
+            self.context.student)
+
 class StudentBaseManageFormPage(KofaEditFormPage):
     """ View to manage student base data
@@ -1234,5 +1256,5 @@
         if error is not None:
             self.flash(error)
-            if 'Would you like' in error:
+            if 'previous session' in error:
                 self.redirect(self.url(self.context) + '/@@addpp')
                 return
@@ -1409,4 +1431,5 @@
     buttonname = _('Create bed ticket')
     notice = ''
+    with_ac = True
 
     def update(self, SUBMIT=None):
@@ -1449,13 +1472,15 @@
             self.redirect(self.url(self.context))
             return
-        self.ac_series = self.request.form.get('ac_series', None)
-        self.ac_number = self.request.form.get('ac_number', None)
+        if self.with_ac:
+            self.ac_series = self.request.form.get('ac_series', None)
+            self.ac_number = self.request.form.get('ac_number', None)
         if SUBMIT is None:
             return
-        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
-        code = get_access_code(pin)
-        if not code:
-            self.flash(_('Activation code is invalid.'))
-            return
+        if self.with_ac:
+            pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
+            code = get_access_code(pin)
+            if not code:
+                self.flash(_('Activation code is invalid.'))
+                return
         # Search and book bed
         cat = queryUtility(ICatalog, name='beds_catalog', default=None)
@@ -1463,5 +1488,5 @@
             owner=(student.student_id,student.student_id))
         if len(entries):
-            # If bed space has bee manually allocated use this bed
+            # If bed space has been manually allocated use this bed
             bed = [entry for entry in entries][0]
         else:
@@ -1479,24 +1504,26 @@
                     mapping = {'a':acc_details['bt']}))
                 return
-        # Mark pin as used (this also fires a pin related transition)
-        if code.state == USED:
-            self.flash(_('Activation code has already been used.'))
-            return
-        else:
-            comment = _(u'invalidated')
-            # Here we know that the ac is in state initialized so we do not
-            # expect an exception, but the owner might be different
-            if not invalidate_accesscode(
-                pin,comment,self.context.student.student_id):
-                self.flash(_('You are not the owner of this access code.'))
+        if self.with_ac:
+            # Mark pin as used (this also fires a pin related transition)
+            if code.state == USED:
+                self.flash(_('Activation code has already been used.'))
                 return
+            else:
+                comment = _(u'invalidated')
+                # Here we know that the ac is in state initialized so we do not
+                # expect an exception, but the owner might be different
+                if not invalidate_accesscode(
+                    pin,comment,self.context.student.student_id):
+                    self.flash(_('You are not the owner of this access code.'))
+                    return
         # Create bed ticket
         bedticket = createObject(u'waeup.BedTicket')
-        bedticket.booking_code = pin
+        if self.with_ac:
+            bedticket.booking_code = pin
         bedticket.booking_session = acc_details['booking_session']
         bedticket.bed_type = acc_details['bt']
         bedticket.bed = bed
         hall_title = bed.__parent__.hostel_name
-        coordinates = bed.getBedCoordinates()[1:]
+        coordinates = bed.coordinates[1:]
         block, room_nr, bed_nr = coordinates
         bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
@@ -1521,4 +1548,5 @@
     grok.require('waeup.handleAccommodation')
     form_fields = grok.AutoFields(IBedTicket)
+    form_fields['booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
     pnav = 4
 
@@ -1547,7 +1575,10 @@
     def label(self):
         portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
-        return translate(_('Bed Allocation: '),
+        #return translate(_('Bed Allocation: '),
+        #    'waeup.kofa', target_language=portal_language) \
+        #    + ' %s' % self.context.bed_coordinates
+        return translate(_('Bed Allocation Slip'),
             'waeup.kofa', target_language=portal_language) \
-            + ' %s' % self.context.bed_coordinates
+            + ' %s' % self.context.getSessionString()
 
     def render(self):
@@ -1614,5 +1645,5 @@
         self.context.bed = new_bed
         hall_title = new_bed.__parent__.hostel_name
-        coordinates = new_bed.getBedCoordinates()[1:]
+        coordinates = new_bed.coordinates[1:]
         block, room_nr, bed_nr = coordinates
         bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser_templates/basepage.pt
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser_templates/basepage.pt	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser_templates/basepage.pt	(revision 9208)
@@ -18,5 +18,5 @@
           <tal:password replace="view/hasPassword" />
       </td>
-    <tr>
+    </tr>
     <tal:files content="structure provider:files" />
   </tbody>
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/interfaces.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/interfaces.py	(revision 9208)
@@ -156,4 +156,5 @@
     current_level = Attribute('The current level of the student')
     current_mode = Attribute('The current mode of the student')
+    current_verdict = Attribute('The current verdict of the student')
     fullname = Attribute('All name parts separated by hyphens')
     display_fullname = Attribute('The fullname of an applicant')
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/student.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/student.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/student.py	(revision 9208)
@@ -134,4 +134,10 @@
         level = getattr(
             self.get('studycourse', None), 'current_level', None)
+        return level
+
+    @property
+    def current_verdict(self):
+        level = getattr(
+            self.get('studycourse', None), 'current_verdict', None)
         return level
 
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/tests/test_browser.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/tests/test_browser.py	(revision 9208)
@@ -403,4 +403,13 @@
 
     layer = FunctionalLayer
+
+    def test_student_properties(self):
+        self.student['studycourse'].current_level = 100
+        self.assertEqual(self.student.current_level, 100)
+        self.student['studycourse'].current_session = 2011
+        self.assertEqual(self.student.current_session, 2011)
+        self.student['studycourse'].current_verdict = 'A'
+        self.assertEqual(self.student.current_verdict, 'A')
+        return
 
     def test_basic_auth(self):
@@ -1183,5 +1192,5 @@
         self.assertTrue(
             'You logged in.' in self.browser.contents)
-        # Student can upload a passport picture
+        # Admitted student can upload a passport picture
         self.browser.open(self.student_path + '/change_portrait')
         ctrl = self.browser.getControl(name='passportuploadedit')
@@ -1194,5 +1203,11 @@
             '<img align="middle" height="125px" src="passport.jpg" />'
             in self.browser.contents)
+        # Students can open admission letter
+        self.browser.getLink("Base Data").click()
+        self.browser.getLink("Download admission letter").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
         # Student can view the clearance data
+        self.browser.open(self.student_path)
         self.browser.getLink("Clearance Data").click()
         # Student can't open clearance edit form before starting clearance
@@ -1584,4 +1599,16 @@
 
     def test_student_previous_payments(self):
+        configuration = createObject('waeup.SessionConfiguration')
+        configuration.academic_session = 2000
+        configuration.clearance_fee = 3456.0
+        configuration.booking_fee = 123.4
+        self.student['studycourse'].entry_session = 2002
+        self.app['configuration'].addSessionConfiguration(configuration)
+        configuration2 = createObject('waeup.SessionConfiguration')
+        configuration2.academic_session = 2003
+        configuration2.clearance_fee = 3456.0
+        configuration2.booking_fee = 123.4
+        self.student['studycourse'].entry_session = 2002
+        self.app['configuration'].addSessionConfiguration(configuration2)
         # Login
         self.browser.open(self.login_path)
@@ -1606,5 +1633,17 @@
         # Previous session payment form is provided
         self.browser.getControl(name="form.p_category").value = ['schoolfee']
+        self.browser.getControl(name="form.p_session").value = ['2000']
+        self.browser.getControl(name="form.p_level").value = ['300']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...The previous session must not fall below...',
+                           self.browser.contents)
+        self.browser.getControl(name="form.p_category").value = ['schoolfee']
         self.browser.getControl(name="form.p_session").value = ['2004']
+        self.browser.getControl(name="form.p_level").value = ['300']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...This is not a previous session...',
+                           self.browser.contents)
+        self.browser.getControl(name="form.p_category").value = ['schoolfee']
+        self.browser.getControl(name="form.p_session").value = ['2003']
         self.browser.getControl(name="form.p_level").value = ['300']
         self.browser.getControl("Create ticket").click()
@@ -1619,5 +1658,5 @@
 
         # Payment session is properly set
-        self.assertEqual(self.student['payments'][value].p_session, 2004)
+        self.assertEqual(self.student['payments'][value].p_session, 2003)
         self.assertEqual(self.student['payments'][value].p_level, 300)
 
@@ -1954,5 +1993,5 @@
         self.browser.getControl(name="form.identifier").value = '123'
         self.browser.getControl(name="form.email").value = 'aa@aa.ng'
-        self.browser.getControl("Get login credentials").click()
+        self.browser.getControl("Send login credentials").click()
         self.assertTrue('An email with' in self.browser.contents)
 
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/utils.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/utils.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/utils.py	(revision 9208)
@@ -138,5 +138,24 @@
         f_label = formatted_label(size=12) % _('Study Course')
         f_label = Paragraph(f_label, style["Normal"])
-        f_text = formatted_text(studentview.context.certcode, size=12)
+        f_text = formatted_text(
+            studentview.context['studycourse'].certificate.longtitle(), size=12)
+        f_text = Paragraph(f_text, style["Normal"])
+        data_right.append([f_label,f_text])
+
+        f_label = formatted_label(size=12) % _('Department')
+        f_label = Paragraph(f_label, style["Normal"])
+        f_text = formatted_text(
+            studentview.context[
+            'studycourse'].certificate.__parent__.__parent__.longtitle(),
+            size=12)
+        f_text = Paragraph(f_text, style["Normal"])
+        data_right.append([f_label,f_text])
+
+        f_label = formatted_label(size=12) % _('Faculty')
+        f_label = Paragraph(f_label, style["Normal"])
+        f_text = formatted_text(
+            studentview.context[
+            'studycourse'].certificate.__parent__.__parent__.__parent__.longtitle(),
+            size=12)
         f_text = Paragraph(f_text, style["Normal"])
         data_right.append([f_label,f_text])
@@ -309,4 +328,9 @@
                 return _('Study course data are incomplete.'), None
             if previous_session:
+                if previous_session < student['studycourse'].entry_session:
+                    return _('The previous session must not fall below '
+                             'your entry session.'), None
+                if previous_session > student['studycourse'].current_session - 1:
+                    return _('This is not a previous session.'), None
                 if previous_level == 100:
                     amount = getattr(certificate, 'school_fee_1', 0.0)
@@ -328,5 +352,8 @@
                     amount = getattr(certificate, 'school_fee_2', 0.0)
         elif category == 'clearance':
-            p_item = student['studycourse'].certificate.code
+            try:
+                p_item = student['studycourse'].certificate.code
+            except (AttributeError, TypeError):
+                return _('Study course data are incomplete.'), None
             amount = academic_session.clearance_fee
         elif category == 'bed_allocation':
@@ -371,6 +398,6 @@
         entry_session = studycourse.entry_session
         current_level = studycourse.current_level
-        if not (entry_session and current_level and certificate):
-            return
+        if None in (entry_session, current_level, certificate):
+            return d
         end_level = certificate.end_level
         if current_level == 10:
@@ -398,4 +425,55 @@
         return available_beds[0]
 
+    def renderPDFAdmissionLetter(self, view, student=None):
+        """Render pdf admission letter.
+        """
+        # XXX: we have to fix the import problems here.
+        from waeup.kofa.browser.interfaces import IPDFCreator
+        from waeup.kofa.browser.pdf import format_html, NOTE_STYLE
+        if student is None:
+            return
+        style = getSampleStyleSheet()
+        creator = getUtility(IPDFCreator)
+        data = []
+        doc_title = view.label
+        author = '%s (%s)' % (view.request.principal.title,
+                              view.request.principal.id)
+        footer_text = view.label
+        if getattr(student, 'student_id', None) is not None:
+            footer_text = "%s - %s - " % (student.student_id, footer_text)
+
+        # Admission text
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        inst_name = grok.getSite()['configuration'].name
+        text = trans(_(
+            'This is to inform you that you have been provisionally'
+            ' admitted into ${a} as follows:', mapping = {'a': inst_name}),
+            portal_language)
+        html = format_html(text)
+        data.append(Paragraph(html, NOTE_STYLE))
+        data.append(Spacer(1, 20))
+
+        # Student data
+        data.append(render_student_data(view))
+
+        # Insert history
+        data.append(Spacer(1, 20))
+        datelist = student.history.messages[0].split()[0].split('-')
+        creation_date = u'%s/%s/%s' % (datelist[2], datelist[1], datelist[0])
+        text = trans(_(
+            'Your Kofa student record was created on ${a}.',
+            mapping = {'a': creation_date}),
+            portal_language)
+        html = format_html(text)
+        data.append(Paragraph(html, NOTE_STYLE))
+
+        # Create pdf stream
+        view.response.setHeader(
+            'Content-Type', 'application/pdf')
+        pdf_stream = creator.create_pdf(
+            data, None, doc_title, author=author, footer=footer_text,
+            note=None)
+        return pdf_stream
+
     def renderPDF(self, view, filename='slip.pdf', student=None,
                   studentview=None, tableheader=None, tabledata=None,
@@ -428,11 +506,12 @@
 
         # Insert widgets
-        data.append(Paragraph(view.title, style["Heading3"]))
-        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
-        separators = getattr(self, 'SEPARATORS_DICT', {})
-        table = creator.getWidgetsTable(
-            view.form_fields, view.context, None, lang=portal_language,
-            separators=separators)
-        data.append(table)
+        if view.form_fields:
+            data.append(Paragraph(view.title, style["Heading3"]))
+            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+            separators = getattr(self, 'SEPARATORS_DICT', {})
+            table = creator.getWidgetsTable(
+                view.form_fields, view.context, None, lang=portal_language,
+                separators=separators)
+            data.append(table)
 
         # Insert scanned docs
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/viewlets.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/viewlets.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/viewlets.py	(revision 9208)
@@ -150,5 +150,5 @@
 
 class ContactActionButton(ManageActionButton):
-    grok.order(4)
+    grok.order(5)
     grok.context(IStudent)
     grok.view(StudentBaseDisplayFormPage)
@@ -166,6 +166,15 @@
     target = 'manage_base'
 
+class AdmissionSlipActionButton(ManageActionButton):
+    grok.order(4)
+    grok.context(IStudent)
+    grok.view(StudentBaseDisplayFormPage)
+    grok.require('waeup.viewStudent')
+    icon = 'actionicon_pdf.png'
+    text = _('Download admission letter')
+    target = 'admission_slip.pdf'
+
 class StudentTransfernButton(ManageActionButton):
-    grok.order(5)
+    grok.order(6)
     grok.context(IStudent)
     grok.view(StudentBaseDisplayFormPage)
@@ -176,5 +185,5 @@
 
 class StudentDeactivateActionButton(ManageActionButton):
-    grok.order(6)
+    grok.order(7)
     grok.context(IStudent)
     grok.view(StudentBaseDisplayFormPage)
@@ -196,5 +205,5 @@
 
 class StudentActivateActionButton(ManageActionButton):
-    grok.order(6)
+    grok.order(7)
     grok.context(IStudent)
     grok.view(StudentBaseDisplayFormPage)
@@ -624,11 +633,11 @@
     @property
     def targets(self):
-        student_url = self.view.application_url() + (
-            '/students/%s' % self.request.principal.id)
-        #app_slip = getUtility(IExtFileStore).getFileByContext(
-        #    self.context.student, 'application_slip')
+        student = grok.getSite()['students'][self.request.principal.id]
+        student_url = self.view.url(student)
+        app_slip = getUtility(IExtFileStore).getFileByContext(
+            student, 'application_slip')
         targets = []
-        #if app_slip:
-        #    targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
+        if app_slip:
+            targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
         targets += [
             {'url':student_url, 'title':'Base Data'},
Index: main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/userscontainer.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/userscontainer.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/userscontainer.py	(revision 9208)
@@ -112,5 +112,4 @@
         If `filepath` is ``None``, a raw string with CSV data is returned.
         """
-        writer, outfile = self.get_csv_writer(filepath)
         users = site.get('users', {})
         return self.export(users.values(), filepath)
Index: main/waeup.kofa/branches/uli-async-update/tools/fix_import_file.py
===================================================================
--- main/waeup.kofa/branches/uli-async-update/tools/fix_import_file.py	(revision 9207)
+++ main/waeup.kofa/branches/uli-async-update/tools/fix_import_file.py	(revision 9208)
@@ -102,4 +102,5 @@
     'type': 'company',
     'date': 'date',
+    'core_or_elective': 'bool',
     'category': 'p_category',
     'reg_transition': 'reg_state',  # we completely change this column,
@@ -205,5 +206,5 @@
         """
         if len(value) == 7:
-            return 'B' + value
+            return 'K' + value
         return value
 
@@ -324,4 +325,14 @@
         if value == 'True':
             return 'yes'
+        return
+
+    @classmethod
+    def bool(self, value, row):
+        """ True --> 1
+        """
+        if value in ('TRUE', 'True'):
+            return '1'
+        elif value in ('FALSE', 'False'):
+            return '0'
         return
 
