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

Last change on this file since 9641 was 9448, checked in by Henrik Bettermann, 12 years ago

Add missing catalog modifier and add some kind of regression test that this never happens again.

  • Property svn:keywords set to Id
File size: 10.6 KB
Line 
1## $Id: hostel.py 9448 2012-10-28 20:12:11Z 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
24from zope.component.interfaces import IFactory
25from datetime import datetime
26from waeup.kofa.utils.helpers import attrs_to_fields
27from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
28from waeup.kofa.hostels.interfaces import IHostel, IBed
29from waeup.kofa.students.interfaces import IBedTicket
30from waeup.kofa.interfaces import IKofaUtils
31from waeup.kofa.interfaces import MessageFactory as _
32from waeup.kofa.utils.helpers import now
33
34class Hostel(grok.Container):
35    """This is a hostel.
36    """
37    grok.implements(IHostel)
38    grok.provides(IHostel)
39
40    def loggerInfo(self, ob_class, comment=None):
41        target = self.__name__
42        return grok.getSite()['hostels'].logger_info(ob_class,target,comment)
43
44    @property
45    def bed_statistics(self):
46        total = len(self.keys())
47        booked = 0
48        for value in self.values():
49            if value.owner != NOT_OCCUPIED:
50                booked += 1
51        return {'booked':booked, 'total':total}
52
53    def clearHostel(self):
54        """Remove all beds
55        """
56        keys = [i for i in self.keys()] # create deep copy
57        for bed in keys:
58            del self[bed]
59        return
60
61    def addBed(self, bed):
62        """Add a bed.
63        """
64        if not IBed.providedBy(bed):
65            raise TypeError(
66                'Hostels contain only IBed instances')
67        self[bed.bed_id] = bed
68        return
69
70    def updateBeds(self):
71        """Fill hostel with beds or update beds.
72        """
73        added_counter = 0
74        modified_counter = 0
75        removed_counter = 0
76        modified_beds = u''
77
78        # Remove all empty beds. Occupied beds remain in hostel!
79        keys = list(self.keys()) # create list copy
80        for key in keys:
81            if self[key].owner == NOT_OCCUPIED:
82                del self[key]
83                self._p_changed = True
84                removed_counter += 1
85            else:
86                self[key].bed_number = 9999
87        remaining = len(keys) - removed_counter
88
89        blocks_for_female = getattr(self,'blocks_for_female',[])
90        blocks_for_male = getattr(self,'blocks_for_male',[])
91        beds_for_fresh = getattr(self,'beds_for_fresh',[])
92        beds_for_pre = getattr(self,'beds_for_pre',[])
93        beds_for_returning = getattr(self,'beds_for_returning',[])
94        beds_for_final = getattr(self,'beds_for_final',[])
95        beds_for_all = getattr(self,'beds_for_all',[])
96        beds_reserved = getattr(self,'beds_reserved',[])
97        all_blocks = blocks_for_female + blocks_for_male
98        all_beds = (beds_for_pre + beds_for_fresh +
99            beds_for_returning + beds_for_final + beds_for_all)
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*100 + room
108                        bt = 'all'
109                        if '%s_%s_%s' % (block,room_nr,bed) in beds_reserved:
110                            bt = "reserved"
111                        elif bed in beds_for_fresh:
112                            bt = 'fr'
113                        elif bed in beds_for_pre:
114                            bt = 'pr'
115                        elif bed in beds_for_final:
116                            bt = 'fi'
117                        elif bed in beds_for_returning:
118                            bt = 're'
119                        bt = u'%s_%s_%s' % (self.special_handling,sex,bt)
120                        uid = u'%s_%s_%d_%s' % (self.hostel_id,block,room_nr,bed)
121                        if self.has_key(uid):
122                            bed = self[uid]
123                            # Renumber remaining beds
124                            bed.bed_number = len(self) + 1 - remaining
125                            remaining -= 1
126                            if bed.bed_type != bt:
127                                bed.bed_type = bt
128                                modified_counter += 1
129                                modified_beds += '%s, ' % uid
130                                notify(grok.ObjectModifiedEvent(bed))
131                        else:
132                            bed = Bed()
133                            bed.bed_id = uid
134                            bed.bed_type = bt
135                            bed.bed_number = len(self) + 1 - remaining
136                            bed.owner = NOT_OCCUPIED
137                            self.addBed(bed)
138                            added_counter +=1
139        return removed_counter, added_counter, modified_counter, modified_beds
140
141Hostel = attrs_to_fields(Hostel)
142
143class Bed(grok.Container):
144    """This is a bed.
145    """
146    grok.implements(IBed)
147    grok.provides(IBed)
148
149    @property
150    def coordinates(self):
151        """Determine the coordinates from the bed_id.
152        """
153        return self.bed_id.split('_')
154
155    # The following property attributes are only needed
156    # for the exporter to ease evaluation with  Excel.
157
158    @property
159    def hall(self):
160        return self.coordinates[0]
161
162    @property
163    def block(self):
164        return self.coordinates[1]
165
166    @property
167    def room(self):
168        return self.coordinates[2]
169
170    @property
171    def bed(self):
172        return self.coordinates[3]
173
174    @property
175    def special_handling(self):
176        return self.bed_type.split('_')[0]
177
178    @property
179    def sex(self):
180        return self.bed_type.split('_')[1]
181
182    @property
183    def bt(self):
184        return self.bed_type.split('_')[2]
185
186
187    def bookBed(self, student_id):
188        if self.owner == NOT_OCCUPIED:
189            self.owner = student_id
190            notify(grok.ObjectModifiedEvent(self))
191            return None
192        else:
193            return self.owner
194
195    def switchReservation(self):
196        """Reserves or unreserve bed respectively.
197        """
198        sh, sex, bt = self.bed_type.split('_')
199        hostel_id, block, room_nr, bed = self.coordinates
200        hostel = self.__parent__
201        beds_for_fresh = getattr(hostel,'beds_for_fresh',[])
202        beds_for_pre = getattr(hostel,'beds_for_pre',[])
203        beds_for_returning = getattr(hostel,'beds_for_returning',[])
204        beds_for_final = getattr(hostel,'beds_for_final',[])
205        bed_string = u'%s_%s_%s' % (block, room_nr, bed)
206        if bt == 'reserved':
207            bt = 'all'
208            if bed in beds_for_fresh:
209                bt = 'fr'
210            elif bed in beds_for_pre:
211                bt = 'pr'
212            elif bed in beds_for_final:
213                bt = 'fi'
214            elif bed in beds_for_returning:
215                bt = 're'
216            bt = u'%s_%s_%s' % (sh, sex, bt)
217
218            # Comment of Martijn:
219            # If you have a non-Persistent subobject (like a list) and
220            # you change it, you need to manually flag the persistence
221            # machinery on the object that its subobject changed, with
222            # _p_changed. This is only necessary if some of the
223            # objects are not sublclasses of Persistent. For common
224            # built-in collections in Python such as list and
225            # dictionary there are replacements (PersistentList,
226            # PersistentMapping), and more advanced building blocks
227            # for indexes (BTrees), that don't have this issue.
228            #hostel._p_changed = True
229
230            # Henrik: Unfortunately, this doesn't work in our case.
231            # After restarting the portal,
232            # added or removed list items are gone. The only way to ensure
233            # persistance is to reassign the list attribute.
234            br = hostel.beds_reserved
235            br.remove(bed_string)
236            hostel.beds_reserved = br
237            message = _(u'unreserved')
238        else:
239            bt = u'%s_%s_reserved' % (sh, sex)
240            # See comment above
241            br = hostel.beds_reserved
242            br.append(bed_string)
243            hostel.beds_reserved = br
244            message = _(u'reserved')
245        self.bed_type = bt
246        notify(grok.ObjectModifiedEvent(self))
247        return message
248
249    def releaseBed(self):
250        if self.owner == NOT_OCCUPIED:
251            return
252        else:
253            old_owner = self.owner
254            self.owner = NOT_OCCUPIED
255            notify(grok.ObjectModifiedEvent(self))
256            accommodation_session = grok.getSite()[
257                'hostels'].accommodation_session
258            try:
259                bedticket = grok.getSite()['students'][old_owner][
260                              'accommodation'][str(accommodation_session)]
261            except KeyError:
262                return '%s without bed ticket' % old_owner
263            bedticket.bed = None
264            tz = getUtility(IKofaUtils).tzinfo
265            timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
266            bedticket.bed_coordinates = u'-- booking cancelled on %s --' % (
267                timestamp,)
268            return old_owner
269
270    def loggerInfo(self, ob_class, comment=None):
271        target = self.__name__
272        return grok.getSite()['hostels'].logger_info(ob_class,target,comment)
273
274Bed = attrs_to_fields(Bed)
275
276class HostelFactory(grok.GlobalUtility):
277    """A factory for hostels.
278
279    We need this factory for the hostel processor.
280    """
281    grok.implements(IFactory)
282    grok.name(u'waeup.Hostel')
283    title = u"Create a new hostel.",
284    description = u"This factory instantiates new hostel instances."
285
286    def __call__(self, *args, **kw):
287        return Hostel()
288
289    def getInterfaces(self):
290        return implementedBy(Hostel)
291
292
293@grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
294def handle_bedticket_removed(bedticket, event):
295    """If a bed ticket is deleted, we make sure that also the owner attribute
296    of the bed is cleared (set to NOT_OCCUPIED).
297    """
298    if bedticket.bed != None:
299        bedticket.bed.owner = NOT_OCCUPIED
300        notify(grok.ObjectModifiedEvent(bedticket.bed))
301
Note: See TracBrowser for help on using the repository browser.