Index: main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 13445)
+++ main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 13455)
@@ -361,18 +361,18 @@
 ------------------
 
-Officers with `ManageHostels` permission do see a 'Relocate student'
-link button which calls the `BedTicketRelocationView`. This view
-relocates the student if student parameters or the bed type of the
-bed have changed. The `update` method of this view checks first, if
-the student has a 'reserved' bed space. Students in reserved beds
-are never subject to relocation. It checks secondly, if booking has
-been cancelled in the accommodation section but other bed space has
-been manually allocated after cancellation. Then this bed is used,
-no matter whether the bed meets the bed type criteria or not. If
-both checks are negative, Kofa searches for a free bed space, which
-meets the student's bed type criteria. Only if it finds a new and
-free bed space, it starts the relocation process by releasing the
-old bed, booking the new bed and designating the new bed in the bed
-ticket.
+Officers with `ManageHostels` permission do see a 'Relocate student' link
+button which calls the `BedTicketRelocationView`. This view relocates the
+student if student parameters or the bed type of the bed have changed. The
+`update` method of this view calls the
+:py:meth:`BedTicket.relocateStudent<waeup.kofa.students.accommodation.BedTicket.relocateStudent>`
+method which checks first, if the student has a 'reserved' bed space.
+Students in reserved beds are never subject to relocation. It checks secondly,
+if booking has been cancelled in the accommodation section but other bed space
+has been manually allocated after cancellation. Then this bed is used, no
+matter whether the bed meets the bed type criteria or not. If both checks are
+negative, Kofa searches for a free bed space, which meets the student's bed
+type criteria. Only if it finds a new and free bed space, it starts the
+relocation process by releasing the old bed, booking the new bed and
+designating the new bed in the bed ticket.
 
 .. seealso::
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/accommodation.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/accommodation.py	(revision 13445)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/accommodation.py	(revision 13455)
@@ -21,11 +21,17 @@
 from datetime import datetime
 import grok
-from zope.component import getUtility
+from zope.component import getUtility, queryUtility
 from zope.component.interfaces import IFactory
+from zope.event import notify
+from zope.i18n import translate
+from zope.catalog.interfaces import ICatalog
 from zope.interface import implementedBy
 from waeup.kofa.interfaces import academic_sessions_vocab, IKofaUtils
+from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.students.interfaces import (
     IStudentAccommodation, IStudentNavigation, IBedTicket, IStudentsUtils)
 from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.hostels.hostel import NOT_OCCUPIED
+
 
 class StudentAccommodation(grok.Container):
@@ -94,4 +100,63 @@
         return False
 
+    def relocateStudent(self):
+        """Relocate student if student parameters have changed or the bed_type
+        of the bed has changed.
+        """
+        if self.booking_session != grok.getSite()[
+            'hostels'].accommodation_session:
+            return False, _("Previous session bookings can't be changed.")
+        student = self.student
+        students_utils = getUtility(IStudentsUtils)
+        acc_details  = students_utils.getAccommodationDetails(student)
+        if self.bed != None and \
+              'reserved' in self.bed.bed_type:
+            return False, _("Students in reserved beds can't be relocated.")
+        if acc_details['bt'] == self.bed_type and \
+                self.bed != None and \
+                self.bed.bed_type == self.bed_type and \
+                self.bed.__parent__.__parent__:
+            return False, _("Student can't be relocated.")
+        # Search a bed
+        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
+        entries = cat.searchResults(
+            owner=(student.student_id,student.student_id))
+        if len(entries) and self.bed == None:
+            # If booking has been cancelled but other bed space has been
+            # manually allocated after cancellation use this bed
+            new_bed = [entry for entry in entries][0]
+        else:
+            # Search for other available beds
+            entries = cat.searchResults(
+                bed_type=(acc_details['bt'],acc_details['bt']))
+            available_beds = [
+                entry for entry in entries if entry.owner == NOT_OCCUPIED]
+            if available_beds:
+                new_bed = students_utils.selectBed(available_beds)
+                new_bed.bookBed(student.student_id)
+            else:
+                return False, _('There is no free bed in your category ${a}.',
+                                 mapping = {'a':acc_details['bt']})
+        # Release old bed if exists
+        if self.bed != None:
+            self.bed.owner = NOT_OCCUPIED
+            notify(grok.ObjectModifiedEvent(self.bed))
+        # Designate new bed in ticket
+        self.bed_type = acc_details['bt']
+        self.bed = new_bed
+        hall_title = new_bed.__parent__.hostel_name
+        coordinates = new_bed.coordinates[1:]
+        block, room_nr, bed_nr = coordinates
+        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
+            'a':hall_title, 'b':block,
+            'c':room_nr, 'd':bed_nr,
+            'e':new_bed.bed_type})
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        self.bed_coordinates = translate(
+            bc, 'waeup.kofa',target_language=portal_language)
+        self.writeLogMessage(self, 'relocated: %s' % new_bed.bed_id)
+        return True, _('Student relocated: ${a}',
+                 mapping = {'a':self.display_coordinates})
+
     def writeLogMessage(self, view, message):
         return self.__parent__.__parent__.writeLogMessage(view, message)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 13445)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 13455)
@@ -2159,63 +2159,9 @@
     # of the bed has changed
     def update(self):
-        student = self.context.student
-        students_utils = getUtility(IStudentsUtils)
-        acc_details  = students_utils.getAccommodationDetails(student)
-        if self.context.bed != None and \
-              'reserved' in self.context.bed.bed_type:
-            self.flash(_("Students in reserved beds can't be relocated."),
-                       type="warning")
-            self.redirect(self.url(self.context))
-            return
-        if acc_details['bt'] == self.context.bed_type and \
-                self.context.bed != None and \
-                self.context.bed.bed_type == self.context.bed_type and \
-                self.context.bed.__parent__.__parent__ :
-            self.flash(_("Student can't be relocated."), type="warning")
-            self.redirect(self.url(self.context))
-            return
-        # Search a bed
-        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
-        entries = cat.searchResults(
-            owner=(student.student_id,student.student_id))
-        if len(entries) and self.context.bed == None:
-            # If booking has been cancelled but other bed space has been
-            # manually allocated after cancellation use this bed
-            new_bed = [entry for entry in entries][0]
-        else:
-            # Search for other available beds
-            entries = cat.searchResults(
-                bed_type=(acc_details['bt'],acc_details['bt']))
-            available_beds = [
-                entry for entry in entries if entry.owner == NOT_OCCUPIED]
-            if available_beds:
-                students_utils = getUtility(IStudentsUtils)
-                new_bed = students_utils.selectBed(available_beds)
-                new_bed.bookBed(student.student_id)
-            else:
-                self.flash(_('There is no free bed in your category ${a}.',
-                    mapping = {'a':acc_details['bt']}), type="warning")
-                self.redirect(self.url(self.context))
-                return
-        # Release old bed if exists
-        if self.context.bed != None:
-            self.context.bed.owner = NOT_OCCUPIED
-            notify(grok.ObjectModifiedEvent(self.context.bed))
-        # Designate new bed in ticket
-        self.context.bed_type = acc_details['bt']
-        self.context.bed = new_bed
-        hall_title = new_bed.__parent__.hostel_name
-        coordinates = new_bed.coordinates[1:]
-        block, room_nr, bed_nr = coordinates
-        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
-            'a':hall_title, 'b':block,
-            'c':room_nr, 'd':bed_nr,
-            'e':new_bed.bed_type})
-        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
-        self.context.bed_coordinates = translate(
-            bc, 'waeup.kofa',target_language=portal_language)
-        self.context.writeLogMessage(self, 'relocated: %s' % new_bed.bed_id)
-        self.flash(_('Student relocated: ${a}',
-            mapping = {'a':self.context.display_coordinates}))
+        success, msg = self.context.relocateStudent()
+        if not success:
+            self.flash(msg, type="warning")
+        else:
+            self.flash(msg)
         self.redirect(self.url(self.context))
         return
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/accommodationmanagepage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/accommodationmanagepage.pt	(revision 13445)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/accommodationmanagepage.pt	(revision 13455)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 13445)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 13455)
@@ -1256,5 +1256,5 @@
         # Relocation is properly logged
         logcontent = open(logfile).read()
-        self.assertTrue('zope.mgr - students.browser.BedTicketRelocationView '
+        self.assertTrue('zope.mgr - students.accommodation.BedTicket '
             '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
         # The payment object still shows the original payment item
