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

Last change on this file since 15790 was 15779, checked in by Henrik Bettermann, 5 years ago

With disable_broken_handlers in /kofa/mety.py the former HostelsPlugin? is working.

  • Property svn:keywords set to Id
File size: 13.6 KB
RevLine 
[7195]1## $Id: hostel.py 15779 2019-11-08 05:22:32Z 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
[6974]200    def switchReservation(self):
[7003]201        """Reserves or unreserve bed respectively.
[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)
[6974]211        if bt == 'reserved':
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)
[7718]222            message = _(u'unreserved')
223        else:
224            bt = u'%s_%s_reserved' % (sh, sex)
225            message = _(u'reserved')
[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):
[13533]253        """Release bed if maintenance fee has not been paid on time.
254        Reserve bed so that it cannot be automatically booked by someone else.
255        """
[13316]256        if self.owner == NOT_OCCUPIED:
257            return
258        accommodation_session = grok.getSite()[
259            'hostels'].accommodation_session
260        try:
261            bedticket = grok.getSite()['students'][self.owner][
262                          'accommodation'][str(accommodation_session)]
263        except KeyError:
[15417]264            self.owner = NOT_OCCUPIED
265            sh, sex, bt = self.bed_type.split('_')
266            bt = u'%s_%s_reserved' % (sh, sex)
267            self.bed_type = bt
268            notify(grok.ObjectModifiedEvent(self))
[13316]269            return
270        if bedticket.maint_payment_made:
271            return
272        jetzt = datetime.utcnow()
273        days_ago = getattr(jetzt - bedticket.booking_date, 'days')
[15305]274        if days_ago >= n:
[13318]275            old_owner = self.owner
[7042]276            self.owner = NOT_OCCUPIED
[13533]277            sh, sex, bt = self.bed_type.split('_')
278            bt = u'%s_%s_reserved' % (sh, sex)
279            self.bed_type = bt
[7042]280            notify(grok.ObjectModifiedEvent(self))
281            bedticket.bed = None
[8183]282            tz = getUtility(IKofaUtils).tzinfo
[8234]283            timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
[13316]284            bedticket.bed_coordinates = u'-- booking expired (%s) --' % (
[8186]285                timestamp,)
[13318]286            return old_owner
[13316]287        return
[7042]288
[13166]289    def writeLogMessage(self, view, message):
290        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
291        self.__parent__.__parent__.logger.info(
292            '%s - %s - %s' % (ob_class, self.__name__, message))
293        return
[6963]294
295Bed = attrs_to_fields(Bed)
[7006]296
[9202]297class HostelFactory(grok.GlobalUtility):
298    """A factory for hostels.
299
300    We need this factory for the hostel processor.
301    """
302    grok.implements(IFactory)
303    grok.name(u'waeup.Hostel')
304    title = u"Create a new hostel.",
305    description = u"This factory instantiates new hostel instances."
306
307    def __call__(self, *args, **kw):
308        return Hostel()
309
310    def getInterfaces(self):
311        return implementedBy(Hostel)
312
[15633]313class BedFactory(grok.GlobalUtility):
314    """A factory for  beds.
[9202]315
[15633]316    We need this factory to ease customization.
317    """
318    grok.implements(IFactory)
319    grok.name(u'waeup.Bed')
320    title = u"Create a new bed.",
321    description = u"This factory instantiates new bed instances."
322
323    def __call__(self, *args, **kw):
324        return Bed()
325
326    def getInterfaces(self):
327        return implementedBy(Bed)
328
[15708]329class HostelsPlugin(grok.GlobalUtility):
330    """A plugin to update beds_catalog
331    """
332    grok.implements(IKofaPluggable)
333    grok.name('hostels')
334    log_prefix = 'HostelsPlugin'
[15633]335
[15708]336    def setup(self, site, name, logger):
337        return
338
339    def update(self, site, name, logger):
340        site_name = getattr(site, '__name__', '<Unnamed Site>')
341        nothing_to_do = True
342        # Add bed_id index
343        try:
344            cat = getUtility(ICatalog, name='beds_catalog')
345            if 'bed_id' not in cat.keys():
346                nothing_to_do = False
[15779]347                # replace original `updateIndex` method
348                def updateIndexReplacement(index):
349                    reindex_cat(cat)
350                cat._updateIndex = cat.updateIndex
351                cat.updateIndex = updateIndexReplacement
352                # setup catalog
353                cat[u'bed_id'] = FieldIndex(field_name=u'bed_id')
354                cat.updateIndex = cat._updateIndex  # undo changes
[15708]355                logger.info(
[15779]356                    '%s: bed_id index added to beds_catalog.'
[15708]357                    % self.log_prefix)
[15779]358                reindex_cat(cat)
359                logger.info(
360                    '%s: beds_catalog updated.'
361                    % self.log_prefix)
[15708]362        except ComponentLookupError: # in unit tests
363            pass
364        if nothing_to_do:
365            logger.info(
366                '%s: Updating site at %s: Nothing to do.' % (
367                    self.log_prefix, site_name,)
368                )
369        return
370
371
[7006]372@grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
373def handle_bedticket_removed(bedticket, event):
374    """If a bed ticket is deleted, we make sure that also the owner attribute
375    of the bed is cleared (set to NOT_OCCUPIED).
376    """
[7068]377    if bedticket.bed != None:
378        bedticket.bed.owner = NOT_OCCUPIED
379        notify(grok.ObjectModifiedEvent(bedticket.bed))
[9202]380
[13440]381@grok.subscribe(IBed, grok.IObjectRemovedEvent)
382def handle_bed_removed(bed, event):
383    """If a bed is deleted, we make sure that the bed object is
384    removed also from the owner's bed ticket.
385    """
386    if bed.owner == NOT_OCCUPIED:
387        return
388    accommodation_session = grok.getSite()['hostels'].accommodation_session
389    try:
390        bedticket = grok.getSite()['students'][bed.owner][
391                      'accommodation'][str(accommodation_session)]
392    except KeyError:
393        return
394    bedticket.bed = None
Note: See TracBrowser for help on using the repository browser.