source: main/waeup.kofa/trunk/src/waeup/kofa/hostels/hostel.py @ 17397

Last change on this file since 17397 was 17313, checked in by Henrik Bettermann, 22 months ago

Allow beds to be blocked so that no student can be allocated to such a bed space (in contrast to reserved beds)

  • Property svn:keywords set to Id
File size: 13.8 KB
RevLine 
[7195]1## $Id: hostel.py 17313 2023-01-25 08:52:51Z henrik $
2##
[6951]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19These are the hostels.
20"""
21import grok
[7003]22from zope.event import notify
[15633]23from zope.component import getUtility, createObject
[15708]24from zope.component.interfaces import IFactory, ComponentLookupError
25from zope.catalog.interfaces import ICatalog
26from zope.catalog.field import FieldIndex
[6951]27from datetime import datetime
[7811]28from waeup.kofa.utils.helpers import attrs_to_fields
29from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
[9414]30from waeup.kofa.hostels.interfaces import IHostel, IBed
[7811]31from waeup.kofa.students.interfaces import IBedTicket
[15708]32from waeup.kofa.interfaces import IKofaUtils, IKofaPluggable
[7811]33from waeup.kofa.interfaces import MessageFactory as _
[15741]34from waeup.kofa.utils.helpers import now, reindex_cat
[6951]35
36class Hostel(grok.Container):
37    """This is a hostel.
38    """
39    grok.implements(IHostel)
40    grok.provides(IHostel)
41
[9196]42    @property
43    def bed_statistics(self):
44        total = len(self.keys())
45        booked = 0
46        for value in self.values():
47            if value.owner != NOT_OCCUPIED:
48                booked += 1
49        return {'booked':booked, 'total':total}
50
51    def clearHostel(self):
[9197]52        """Remove all beds
[9196]53        """
[9197]54        keys = [i for i in self.keys()] # create deep copy
55        for bed in keys:
56            del self[bed]
57        return
[9196]58
[6963]59    def addBed(self, bed):
[6970]60        """Add a bed.
[6963]61        """
62        if not IBed.providedBy(bed):
63            raise TypeError(
64                'Hostels contain only IBed instances')
[6970]65        self[bed.bed_id] = bed
[6963]66        return
67
[6970]68    def updateBeds(self):
69        """Fill hostel with beds or update beds.
70        """
71        added_counter = 0
72        modified_counter = 0
[6978]73        removed_counter = 0
[6988]74        modified_beds = u''
[6978]75
76        # Remove all empty beds. Occupied beds remain in hostel!
77        keys = list(self.keys()) # create list copy
78        for key in keys:
79            if self[key].owner == NOT_OCCUPIED:
80                del self[key]
81                self._p_changed = True
82                removed_counter += 1
[6998]83            else:
84                self[key].bed_number = 9999
85        remaining = len(keys) - removed_counter
[6978]86
[6970]87        blocks_for_female = getattr(self,'blocks_for_female',[])
88        blocks_for_male = getattr(self,'blocks_for_male',[])
89        beds_for_fresh = getattr(self,'beds_for_fresh',[])
90        beds_for_pre = getattr(self,'beds_for_pre',[])
91        beds_for_returning = getattr(self,'beds_for_returning',[])
92        beds_for_final = getattr(self,'beds_for_final',[])
[6971]93        beds_for_all = getattr(self,'beds_for_all',[])
[6970]94        all_blocks = blocks_for_female + blocks_for_male
95        all_beds = (beds_for_pre + beds_for_fresh +
[6971]96            beds_for_returning + beds_for_final + beds_for_all)
[13352]97        floor_base = 100
98        if self.rooms_per_floor > 99:
99            floor_base = 1000
[6970]100        for block in all_blocks:
101            sex = 'male'
102            if block in blocks_for_female:
103                sex = 'female'
104            for floor in range(1,int(self.floors_per_block)+1):
105                for room in range(1,int(self.rooms_per_floor)+1):
106                    for bed in all_beds:
[13352]107                        room_nr = floor*floor_base + room
[6970]108                        bt = 'all'
[13346]109                        if bed in beds_for_fresh:
[6970]110                            bt = 'fr'
111                        elif bed in beds_for_pre:
112                            bt = 'pr'
113                        elif bed in beds_for_final:
114                            bt = 'fi'
115                        elif bed in beds_for_returning:
116                            bt = 're'
[6973]117                        bt = u'%s_%s_%s' % (self.special_handling,sex,bt)
[13168]118                        uid = u'%s_%s_%d_%s' % (
119                            self.hostel_id,block,room_nr,bed)
[9701]120                        if uid in self:
[6970]121                            bed = self[uid]
[13170]122                            # Renumber remaining bed
[6998]123                            bed.bed_number = len(self) + 1 - remaining
124                            remaining -= 1
[6970]125                            if bed.bed_type != bt:
126                                bed.bed_type = bt
127                                modified_counter += 1
[9448]128                                modified_beds += '%s, ' % uid
129                                notify(grok.ObjectModifiedEvent(bed))
[6970]130                        else:
[15633]131                            bed = createObject(u'waeup.Bed')
[6970]132                            bed.bed_id = uid
133                            bed.bed_type = bt
[6998]134                            bed.bed_number = len(self) + 1 - remaining
[6970]135                            bed.owner = NOT_OCCUPIED
136                            self.addBed(bed)
137                            added_counter +=1
[6988]138        return removed_counter, added_counter, modified_counter, modified_beds
[6970]139
[13166]140    def writeLogMessage(self, view, message):
141        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
142        self.__parent__.logger.info(
143            '%s - %s - %s' % (ob_class, self.__name__, message))
144        return
145
[6951]146Hostel = attrs_to_fields(Hostel)
[6963]147
148class Bed(grok.Container):
149    """This is a bed.
150    """
[9414]151    grok.implements(IBed)
[6963]152    grok.provides(IBed)
153
[9199]154    @property
155    def coordinates(self):
[6974]156        """Determine the coordinates from the bed_id.
[6963]157        """
[6974]158        return self.bed_id.split('_')
[6963]159
[9199]160    # The following property attributes are only needed
[13170]161    # for the exporter to ease evaluation with Excel.
[9199]162
163    @property
164    def hall(self):
165        return self.coordinates[0]
166
167    @property
168    def block(self):
169        return self.coordinates[1]
170
171    @property
172    def room(self):
173        return self.coordinates[2]
174
175    @property
176    def bed(self):
177        return self.coordinates[3]
178
179    @property
180    def special_handling(self):
181        return self.bed_type.split('_')[0]
182
183    @property
184    def sex(self):
185        return self.bed_type.split('_')[1]
186
187    @property
188    def bt(self):
189        return self.bed_type.split('_')[2]
190
191
[6996]192    def bookBed(self, student_id):
[6998]193        if self.owner == NOT_OCCUPIED:
194            self.owner = student_id
[7003]195            notify(grok.ObjectModifiedEvent(self))
[6998]196            return None
197        else:
198            return self.owner
[6996]199
[17313]200    def switchBed(self, switch_type):
201        """Switches bed. `switch_type` is either `reserved` or `blocked`.
[6974]202        """
203        sh, sex, bt = self.bed_type.split('_')
[9199]204        hostel_id, block, room_nr, bed = self.coordinates
[6975]205        hostel = self.__parent__
[6988]206        beds_for_fresh = getattr(hostel,'beds_for_fresh',[])
207        beds_for_pre = getattr(hostel,'beds_for_pre',[])
208        beds_for_returning = getattr(hostel,'beds_for_returning',[])
209        beds_for_final = getattr(hostel,'beds_for_final',[])
[6976]210        bed_string = u'%s_%s_%s' % (block, room_nr, bed)
[17313]211        if bt == switch_type:
[6974]212            bt = 'all'
213            if bed in beds_for_fresh:
214                bt = 'fr'
215            elif bed in beds_for_pre:
216                bt = 'pr'
217            elif bed in beds_for_final:
218                bt = 'fi'
219            elif bed in beds_for_returning:
220                bt = 're'
221            bt = u'%s_%s_%s' % (sh, sex, bt)
[17313]222            message = _(u'un' + switch_type)
[7718]223        else:
[17313]224            bt = u'%s_%s_%s' % (sh, sex, switch_type)
225            message = switch_type
[6974]226        self.bed_type = bt
[9448]227        notify(grok.ObjectModifiedEvent(self))
[6988]228        return message
[6974]229
[7042]230    def releaseBed(self):
[13533]231        """Release bed.
232        """
[7042]233        if self.owner == NOT_OCCUPIED:
[7070]234            return
[13316]235        old_owner = self.owner
236        self.owner = NOT_OCCUPIED
237        notify(grok.ObjectModifiedEvent(self))
238        accommodation_session = grok.getSite()[
239            'hostels'].accommodation_session
240        try:
241            bedticket = grok.getSite()['students'][old_owner][
242                          'accommodation'][str(accommodation_session)]
243        except KeyError:
244            return '%s without bed ticket' % old_owner
245        bedticket.bed = None
246        tz = getUtility(IKofaUtils).tzinfo
247        timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
248        bedticket.bed_coordinates = u'-- booking cancelled on %s --' % (
249            timestamp,)
250        return old_owner
251
252    def releaseBedIfMaintenanceNotPaid(self, n=7):
[15944]253        """Release bed if maintenance fee has not been paid on time or
254        if bed ticket does not exist. Reserve bed so that it cannot be
255        automatically booked by someone else.
[13533]256        """
[13316]257        if self.owner == NOT_OCCUPIED:
258            return
259        accommodation_session = grok.getSite()[
260            'hostels'].accommodation_session
261        try:
262            bedticket = grok.getSite()['students'][self.owner][
263                          'accommodation'][str(accommodation_session)]
264        except KeyError:
[15944]265            old_owner = self.owner
[15417]266            self.owner = NOT_OCCUPIED
267            sh, sex, bt = self.bed_type.split('_')
268            bt = u'%s_%s_reserved' % (sh, sex)
269            self.bed_type = bt
270            notify(grok.ObjectModifiedEvent(self))
[15944]271            ##: Add logging message
272            return "%s wob" % old_owner # owner without bed ticket
[13316]273        if bedticket.maint_payment_made:
274            return
275        jetzt = datetime.utcnow()
276        days_ago = getattr(jetzt - bedticket.booking_date, 'days')
[15305]277        if days_ago >= n:
[13318]278            old_owner = self.owner
[7042]279            self.owner = NOT_OCCUPIED
[13533]280            sh, sex, bt = self.bed_type.split('_')
281            bt = u'%s_%s_reserved' % (sh, sex)
282            self.bed_type = bt
[7042]283            notify(grok.ObjectModifiedEvent(self))
284            bedticket.bed = None
[8183]285            tz = getUtility(IKofaUtils).tzinfo
[8234]286            timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
[13316]287            bedticket.bed_coordinates = u'-- booking expired (%s) --' % (
[8186]288                timestamp,)
[13318]289            return old_owner
[13316]290        return
[7042]291
[13166]292    def writeLogMessage(self, view, message):
293        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
294        self.__parent__.__parent__.logger.info(
295            '%s - %s - %s' % (ob_class, self.__name__, message))
296        return
[6963]297
298Bed = attrs_to_fields(Bed)
[7006]299
[9202]300class HostelFactory(grok.GlobalUtility):
301    """A factory for hostels.
302
303    We need this factory for the hostel processor.
304    """
305    grok.implements(IFactory)
306    grok.name(u'waeup.Hostel')
307    title = u"Create a new hostel.",
308    description = u"This factory instantiates new hostel instances."
309
310    def __call__(self, *args, **kw):
311        return Hostel()
312
313    def getInterfaces(self):
314        return implementedBy(Hostel)
315
[15633]316class BedFactory(grok.GlobalUtility):
317    """A factory for  beds.
[9202]318
[15633]319    We need this factory to ease customization.
320    """
321    grok.implements(IFactory)
322    grok.name(u'waeup.Bed')
323    title = u"Create a new bed.",
324    description = u"This factory instantiates new bed instances."
325
326    def __call__(self, *args, **kw):
327        return Bed()
328
329    def getInterfaces(self):
330        return implementedBy(Bed)
331
[15708]332class HostelsPlugin(grok.GlobalUtility):
333    """A plugin to update beds_catalog
334    """
335    grok.implements(IKofaPluggable)
336    grok.name('hostels')
337    log_prefix = 'HostelsPlugin'
[15633]338
[15708]339    def setup(self, site, name, logger):
340        return
341
342    def update(self, site, name, logger):
343        site_name = getattr(site, '__name__', '<Unnamed Site>')
344        nothing_to_do = True
345        # Add bed_id index
346        try:
347            cat = getUtility(ICatalog, name='beds_catalog')
348            if 'bed_id' not in cat.keys():
349                nothing_to_do = False
[15779]350                # replace original `updateIndex` method
351                def updateIndexReplacement(index):
352                    reindex_cat(cat)
353                cat._updateIndex = cat.updateIndex
354                cat.updateIndex = updateIndexReplacement
355                # setup catalog
356                cat[u'bed_id'] = FieldIndex(field_name=u'bed_id')
357                cat.updateIndex = cat._updateIndex  # undo changes
[15708]358                logger.info(
[15779]359                    '%s: bed_id index added to beds_catalog.'
[15708]360                    % self.log_prefix)
[15779]361                reindex_cat(cat)
362                logger.info(
363                    '%s: beds_catalog updated.'
364                    % self.log_prefix)
[15708]365        except ComponentLookupError: # in unit tests
366            pass
367        if nothing_to_do:
368            logger.info(
369                '%s: Updating site at %s: Nothing to do.' % (
370                    self.log_prefix, site_name,)
371                )
372        return
373
374
[7006]375@grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
376def handle_bedticket_removed(bedticket, event):
377    """If a bed ticket is deleted, we make sure that also the owner attribute
378    of the bed is cleared (set to NOT_OCCUPIED).
379    """
[7068]380    if bedticket.bed != None:
381        bedticket.bed.owner = NOT_OCCUPIED
382        notify(grok.ObjectModifiedEvent(bedticket.bed))
[9202]383
[13440]384@grok.subscribe(IBed, grok.IObjectRemovedEvent)
385def handle_bed_removed(bed, event):
386    """If a bed is deleted, we make sure that the bed object is
387    removed also from the owner's bed ticket.
388    """
389    if bed.owner == NOT_OCCUPIED:
390        return
391    accommodation_session = grok.getSite()['hostels'].accommodation_session
392    try:
393        bedticket = grok.getSite()['students'][bed.owner][
394                      'accommodation'][str(accommodation_session)]
395    except KeyError:
396        return
397    bedticket.bed = None
Note: See TracBrowser for help on using the repository browser.