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
Line 
1## $Id: hostel.py 15779 2019-11-08 05:22:32Z henrik $
2##
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
22from zope.event import notify
23from zope.component import getUtility, createObject
24from zope.component.interfaces import IFactory, ComponentLookupError
25from zope.catalog.interfaces import ICatalog
26from zope.catalog.field import FieldIndex
27from datetime import datetime
28from waeup.kofa.utils.helpers import attrs_to_fields
29from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
30from waeup.kofa.hostels.interfaces import IHostel, IBed
31from waeup.kofa.students.interfaces import IBedTicket
32from waeup.kofa.interfaces import IKofaUtils, IKofaPluggable
33from waeup.kofa.interfaces import MessageFactory as _
34from waeup.kofa.utils.helpers import now, reindex_cat
35
36class Hostel(grok.Container):
37    """This is a hostel.
38    """
39    grok.implements(IHostel)
40    grok.provides(IHostel)
41
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):
52        """Remove all beds
53        """
54        keys = [i for i in self.keys()] # create deep copy
55        for bed in keys:
56            del self[bed]
57        return
58
59    def addBed(self, bed):
60        """Add a bed.
61        """
62        if not IBed.providedBy(bed):
63            raise TypeError(
64                'Hostels contain only IBed instances')
65        self[bed.bed_id] = bed
66        return
67
68    def updateBeds(self):
69        """Fill hostel with beds or update beds.
70        """
71        added_counter = 0
72        modified_counter = 0
73        removed_counter = 0
74        modified_beds = u''
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
83            else:
84                self[key].bed_number = 9999
85        remaining = len(keys) - removed_counter
86
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',[])
93        beds_for_all = getattr(self,'beds_for_all',[])
94        all_blocks = blocks_for_female + blocks_for_male
95        all_beds = (beds_for_pre + beds_for_fresh +
96            beds_for_returning + beds_for_final + beds_for_all)
97        floor_base = 100
98        if self.rooms_per_floor > 99:
99            floor_base = 1000
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:
107                        room_nr = floor*floor_base + room
108                        bt = 'all'
109                        if bed in beds_for_fresh:
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'
117                        bt = u'%s_%s_%s' % (self.special_handling,sex,bt)
118                        uid = u'%s_%s_%d_%s' % (
119                            self.hostel_id,block,room_nr,bed)
120                        if uid in self:
121                            bed = self[uid]
122                            # Renumber remaining bed
123                            bed.bed_number = len(self) + 1 - remaining
124                            remaining -= 1
125                            if bed.bed_type != bt:
126                                bed.bed_type = bt
127                                modified_counter += 1
128                                modified_beds += '%s, ' % uid
129                                notify(grok.ObjectModifiedEvent(bed))
130                        else:
131                            bed = createObject(u'waeup.Bed')
132                            bed.bed_id = uid
133                            bed.bed_type = bt
134                            bed.bed_number = len(self) + 1 - remaining
135                            bed.owner = NOT_OCCUPIED
136                            self.addBed(bed)
137                            added_counter +=1
138        return removed_counter, added_counter, modified_counter, modified_beds
139
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
146Hostel = attrs_to_fields(Hostel)
147
148class Bed(grok.Container):
149    """This is a bed.
150    """
151    grok.implements(IBed)
152    grok.provides(IBed)
153
154    @property
155    def coordinates(self):
156        """Determine the coordinates from the bed_id.
157        """
158        return self.bed_id.split('_')
159
160    # The following property attributes are only needed
161    # for the exporter to ease evaluation with Excel.
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
192    def bookBed(self, student_id):
193        if self.owner == NOT_OCCUPIED:
194            self.owner = student_id
195            notify(grok.ObjectModifiedEvent(self))
196            return None
197        else:
198            return self.owner
199
200    def switchReservation(self):
201        """Reserves or unreserve bed respectively.
202        """
203        sh, sex, bt = self.bed_type.split('_')
204        hostel_id, block, room_nr, bed = self.coordinates
205        hostel = self.__parent__
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',[])
210        bed_string = u'%s_%s_%s' % (block, room_nr, bed)
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)
222            message = _(u'unreserved')
223        else:
224            bt = u'%s_%s_reserved' % (sh, sex)
225            message = _(u'reserved')
226        self.bed_type = bt
227        notify(grok.ObjectModifiedEvent(self))
228        return message
229
230    def releaseBed(self):
231        """Release bed.
232        """
233        if self.owner == NOT_OCCUPIED:
234            return
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):
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        """
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:
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))
269            return
270        if bedticket.maint_payment_made:
271            return
272        jetzt = datetime.utcnow()
273        days_ago = getattr(jetzt - bedticket.booking_date, 'days')
274        if days_ago >= n:
275            old_owner = self.owner
276            self.owner = NOT_OCCUPIED
277            sh, sex, bt = self.bed_type.split('_')
278            bt = u'%s_%s_reserved' % (sh, sex)
279            self.bed_type = bt
280            notify(grok.ObjectModifiedEvent(self))
281            bedticket.bed = None
282            tz = getUtility(IKofaUtils).tzinfo
283            timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
284            bedticket.bed_coordinates = u'-- booking expired (%s) --' % (
285                timestamp,)
286            return old_owner
287        return
288
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
294
295Bed = attrs_to_fields(Bed)
296
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
313class BedFactory(grok.GlobalUtility):
314    """A factory for  beds.
315
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
329class HostelsPlugin(grok.GlobalUtility):
330    """A plugin to update beds_catalog
331    """
332    grok.implements(IKofaPluggable)
333    grok.name('hostels')
334    log_prefix = 'HostelsPlugin'
335
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
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
355                logger.info(
356                    '%s: bed_id index added to beds_catalog.'
357                    % self.log_prefix)
358                reindex_cat(cat)
359                logger.info(
360                    '%s: beds_catalog updated.'
361                    % self.log_prefix)
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
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    """
377    if bedticket.bed != None:
378        bedticket.bed.owner = NOT_OCCUPIED
379        notify(grok.ObjectModifiedEvent(bedticket.bed))
380
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.