source: main/waeup.kofa/trunk/src/waeup/kofa/hostels/browser.py @ 13533

Last change on this file since 13533 was 13533, checked in by Henrik Bettermann, 9 years ago

Reserve bed so that it cannot be automatically booked by someone else.

  • Property svn:keywords set to Id
File size: 15.6 KB
Line 
1## $Id: browser.py 13533 2015-12-03 20:04:17Z 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"""UI components for hostels and related components.
19"""
20import grok
21import sys
22from zope.i18n import translate
23from zope.component import getUtility
24from zope.catalog.interfaces import ICatalog
25from waeup.kofa.browser.layout import (
26    KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
27    NullValidator, UtilityView)
28from waeup.kofa.browser.breadcrumbs import Breadcrumb
29from waeup.kofa.browser.layout import default_primary_nav_template
30from waeup.kofa.browser.pages import delSubobjects
31from waeup.kofa.browser.viewlets import (
32    ManageActionButton, PrimaryNavTab)
33from waeup.kofa.browser.layout import jsaction, action
34from waeup.kofa.interfaces import IKofaObject, IKofaUtils, DOCLINK
35from waeup.kofa.interfaces import MessageFactory as _
36from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
37from waeup.kofa.hostels.hostel import Hostel
38from waeup.kofa.hostels.interfaces import (
39    IHostelsContainer, IHostel, IBed)
40from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
41
42# Save function used for save methods in manager pages
43def msave(view, **data):
44    changed_fields = view.applyData(view.context, **data)
45    # Turn list of lists into single list
46    if changed_fields:
47        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
48    fields_string = ' + '.join(changed_fields)
49    view.context._p_changed = True
50    view.flash(_('Form has been saved.'))
51    if fields_string:
52        view.context.writeLogMessage(view, 'saved: %s' % fields_string)
53    return
54
55class HostelsTab(PrimaryNavTab):
56    """Hostels tab in primary navigation.
57    """
58
59    grok.context(IKofaObject)
60    grok.order(5)
61    grok.require('waeup.viewHostels')
62    grok.name('hostelstab')
63    template = default_primary_nav_template
64    pnav = 5
65    tab_title = _(u'Hostels')
66
67    @property
68    def link_target(self):
69        return self.view.application_url('hostels')
70
71class HostelsBreadcrumb(Breadcrumb):
72    """A breadcrumb for the hostels container.
73    """
74    grok.context(IHostelsContainer)
75    title = _(u'Hostels')
76
77class HostelBreadcrumb(Breadcrumb):
78    """A breadcrumb for the hostel container.
79    """
80    grok.context(IHostel)
81
82    def title(self):
83        return self.context.hostel_name
84
85class BedBreadcrumb(Breadcrumb):
86    """A breadcrumb for the hostel container.
87    """
88    grok.context(IBed)
89
90    def title(self):
91        co = self.context.coordinates
92        return _('Block ${a}, Room ${b}, Bed ${c}',
93            mapping = {'a':co[1], 'b':co[2], 'c':co[3]})
94
95class HostelsContainerPage(KofaDisplayFormPage):
96    """The standard view for hostels containers.
97    """
98    grok.context(IHostelsContainer)
99    grok.name('index')
100    grok.require('waeup.viewHostels')
101    grok.template('containerpage')
102    label = _('Accommodation Section')
103    pnav = 5
104    form_fields = grok.AutoFields(IHostelsContainer)
105    form_fields[
106        'startdate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
107    form_fields[
108        'enddate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
109
110class HostelsContainerManageActionButton(ManageActionButton):
111    grok.order(1)
112    grok.context(IHostelsContainer)
113    grok.view(HostelsContainerPage)
114    grok.require('waeup.manageHostels')
115    text = _('Manage accommodation section')
116
117class HostelsStatisticsActionButton(ManageActionButton):
118    grok.order(2)
119    grok.context(IHostelsContainer)
120    grok.view(HostelsContainerPage)
121    grok.require('waeup.manageHostels')
122    icon = 'actionicon_statistics.png'
123    text = _('Bed statistics')
124    target = 'statistics'
125
126class HostelsContainerManagePage(KofaEditFormPage):
127    """The manage page for hostel containers.
128    """
129    grok.context(IHostelsContainer)
130    grok.name('manage')
131    grok.require('waeup.manageHostels')
132    grok.template('containermanagepage')
133    pnav = 5
134    label = _('Manage accommodation section')
135    form_fields = grok.AutoFields(IHostelsContainer)
136    taboneactions = [_('Save')]
137    tabtwoactions = [_('Add hostel'),
138        _('Clear all hostels'),
139        _('Remove selected')]
140    doclink = DOCLINK + '/hostels.html#accommodation-section'
141
142    # It's quite dangerous to remove entire hostels with its content (beds).
143    # Thus, this remove method should be combined with an archiving function.
144    @jsaction(_('Remove selected'))
145    def delHostels(self, **data):
146        form = self.request.form
147        if 'val_id' in form:
148            deleted = []
149            child_id = form['val_id']
150            if not isinstance(child_id, list):
151                child_id = [child_id]
152            for id in child_id:
153                deleted.append(id)
154            self.context.writeLogMessage(
155                self, 'deleted: % s' % ', '.join(deleted))
156        delSubobjects(self, redirect='@@manage', tab='2')
157        return
158
159    @action(_('Add hostel'), validator=NullValidator)
160    def addSubunit(self, **data):
161        self.redirect(self.url(self.context, 'addhostel'))
162        return
163
164    @jsaction(_('Clear all hostels'), style='danger')
165    def clearHostels(self, **data):
166        self.context.clearAllHostels()
167        self.flash(_('All hostels cleared.'))
168        self.context.writeLogMessage(self, 'all hostels cleared')
169        self.redirect(self.url(self.context, '@@manage')+'#tab2')
170        return
171
172    @action(_('Save'), style='primary')
173    def save(self, **data):
174        if data['accommodation_session'] != self.context.accommodation_session:
175            catalog = getUtility(ICatalog, name='beds_catalog')
176            beds = catalog.searchResults(bed_type=(None,None))
177            if len(beds):
178                self.flash(_('You can\'t change the booking session '
179                             'before clearing all hostels.'),
180                           type='warning')
181                return
182        self.applyData(self.context, **data)
183        self.flash(_('Settings have been saved.'))
184        return
185
186class ReleaseExpiredAllocationsPage(UtilityView, grok.View):
187    """Release all expired allocated beds.
188    """
189    grok.context(IHostelsContainer)
190    grok.name('releaseexpired')
191    grok.require('waeup.manageHostels')
192
193    def update(self, n=7):
194        if not grok.getSite()['configuration'].maintmode_enabled_by:
195            self.flash(
196                _('Portal must be in maintenance mode for releasing expired '
197                  'bed allocations.'),
198                type='danger')
199            self.redirect(self.url(self.context))
200            return
201        released = self.context.releaseExpiredAllocations(n)
202        if len(released):
203            message = ', '.join(released)
204            self.context.writeLogMessage(self, 'released: %s' % message)
205            if len(released) > 50:
206                flash_msg = _('Successfully released ${a} beds.',
207                    mapping = {'a':len(released)})
208            else:
209                flash_msg = _('Successfully released beds: ${a}',
210                    mapping = {'a':message})
211        else:
212            flash_msg = _('No bed released.')
213        self.flash(flash_msg, type='success')
214        self.redirect(self.url(self.context))
215        return
216
217    def render(self):
218        return
219
220class HostelsStatisticsPage(KofaDisplayFormPage):
221    """Some statistics about beds in hostels.
222    """
223    grok.context(IHostelsContainer)
224    grok.name('statistics')
225    grok.require('waeup.manageHostels')
226    grok.template('containerstatistics')
227    label = _('Bed Statistics')
228
229class HostelAddFormPage(KofaAddFormPage):
230    """Add-form to add a hostel.
231    """
232    grok.context(IHostelsContainer)
233    grok.require('waeup.manageHostels')
234    grok.name('addhostel')
235    #grok.template('hosteladdpage')
236    form_fields = grok.AutoFields(IHostel).omit('beds_reserved', 'hostel_id')
237    label = _('Add hostel')
238    pnav = 5
239    doclink = DOCLINK + '/hostels.html#accommodation-section'
240
241    @action(_('Create hostel'))
242    def addHostel(self, **data):
243        hostel = Hostel()
244        self.applyData(hostel, **data)
245        hostel.hostel_id = data['hostel_name'].lower().replace(
246            ' ','-').replace('_','-').replace('.','')
247        try:
248            self.context.addHostel(hostel)
249        except KeyError:
250            self.flash(_('The hostel already exists.'), type='warning')
251            return
252        self.flash(_('Hostel created.'))
253        self.context.writeLogMessage(self, 'added: % s' % data['hostel_name'])
254        self.redirect(self.url(self.context[hostel.hostel_id], 'index'))
255        return
256
257class HostelDisplayFormPage(KofaDisplayFormPage):
258    """ Page to display hostel data
259    """
260    grok.context(IHostel)
261    grok.name('index')
262    grok.require('waeup.viewHostels')
263    grok.template('hostelpage')
264    form_fields = grok.AutoFields(IHostel).omit('beds_reserved')
265    pnav = 5
266
267    @property
268    def label(self):
269        return self.context.hostel_name
270
271class HostelManageActionButton(ManageActionButton):
272    grok.order(1)
273    grok.context(IHostel)
274    grok.view(HostelDisplayFormPage)
275    grok.require('waeup.manageHostels')
276    text = _('Manage')
277    target = 'manage'
278
279class HostelManageFormPage(KofaEditFormPage):
280    """ View to edit hostel data
281    """
282    grok.context(IHostel)
283    grok.name('manage')
284    grok.require('waeup.manageHostels')
285    form_fields = grok.AutoFields(IHostel).omit('hostel_id', 'beds_reserved')
286    grok.template('hostelmanagepage')
287    label = _('Manage hostel')
288    pnav = 5
289    taboneactions = [_('Save')]
290    tabtwoactions = [_('Update all beds'),
291        _('Switch reservation of selected beds'),
292        _('Release selected beds'),
293        _('Clear hostel')]
294    not_occupied = NOT_OCCUPIED
295    doclink = DOCLINK + '/hostels.html#browser-pages'
296
297    @property
298    def students_url(self):
299        return self.url(grok.getSite(),'students')
300
301    @action(_('Save'), style='primary')
302    def save(self, **data):
303        msave(self, **data)
304        return
305
306    @action(_('Update all beds'), style='primary',
307            warning=_('Attention: The updater removes all reservation flags of existing beds.'
308                        ' You really want to update?'))
309    def updateBeds(self, **data):
310        if not grok.getSite()['configuration'].maintmode_enabled_by:
311            self.flash(
312                _('Portal must be in maintenance mode for bed updates.'),
313                type='danger')
314            self.redirect(self.url(self.context, '@@manage')+'#tab2')
315            return
316        removed, added, modified, modified_beds = self.context.updateBeds()
317        message = '%d empty beds removed, %d beds added, %d occupied beds modified (%s)' % (
318            removed, added, modified, modified_beds)
319        if modified > 50:
320            flash_message = _(
321                '${a} empty beds removed, ${b} beds added, '
322                + '${c} occupied beds modified',
323                mapping = {'a':removed, 'b':added, 'c':modified})
324        else:
325            flash_message = _(
326                '${a} empty beds removed, ${b} beds added, '
327                + '${c} occupied beds modified (${d})',
328                mapping = {'a':removed, 'b':added, 'c':modified, 'd':modified_beds})
329        self.flash(flash_message)
330        self.context.writeLogMessage(self, message)
331        self.redirect(self.url(self.context, '@@manage')+'#tab2')
332        return
333
334    @action(_('Switch reservation of selected beds'))
335    def switchReservations(self, **data):
336        form = self.request.form
337        if 'val_id' in form:
338            child_id = form['val_id']
339        else:
340            self.flash(_('No item selected.'), type='warning')
341            self.redirect(self.url(self.context, '@@manage')+'#tab2')
342            return
343        if not isinstance(child_id, list):
344            child_id = [child_id]
345        switched = [] # for log file
346        switched_translated = [] # for flash message
347        # Here we know that the cookie has been set
348        preferred_language = self.request.cookies.get('kofa.language')
349        for bed_id in child_id:
350            message = self.context[bed_id].switchReservation()
351            switched.append('%s (%s)' % (bed_id,message))
352            m_translated = translate(message, 'waeup.kofa',
353                target_language=preferred_language)
354            switched_translated.append('%s (%s)' % (bed_id,m_translated))
355        if len(switched):
356            message = ', '.join(switched)
357            m_translated = ', '.join(switched_translated)
358            if len(switched) > 50:
359                self.flash(_('Successfully switched ${a} beds.',
360                    mapping = {'a':len(switched)}))
361            else:
362                self.flash(_('Successfully switched beds: ${a}',
363                    mapping = {'a':m_translated}))
364            self.context.writeLogMessage(self, 'switched: %s' % message)
365            self.redirect(self.url(self.context, '@@manage')+'#tab2')
366        return
367
368    @action(_('Release selected beds'))
369    def releaseBeds(self, **data):
370        form = self.request.form
371        if 'val_id' in form:
372            child_id = form['val_id']
373        else:
374            self.flash(_('No item selected.'), type='warning')
375            self.redirect(self.url(self.context, '@@manage')+'#tab2')
376            return
377        if not isinstance(child_id, list):
378            child_id = [child_id]
379        released = []
380        for bed_id in child_id:
381            message = self.context[bed_id].releaseBed()
382            if message:
383                released.append('%s (%s)' % (bed_id,message))
384        if len(released):
385            message = ', '.join(released)
386            self.flash(_('Successfully released beds: ${a}',
387                mapping = {'a':message}))
388            self.context.writeLogMessage(self, 'released: %s' % message)
389            self.redirect(self.url(self.context, '@@manage')+'#tab2')
390        else:
391            self.flash(_('No allocated bed selected.'), type='warning')
392            self.redirect(self.url(self.context, '@@manage')+'#tab2')
393        return
394
395    @jsaction(_('Clear hostel'), style='danger')
396    def clearHostel(self, **data):
397        self.context.clearHostel()
398        self.flash(_('Hostel cleared.'))
399        self.context.writeLogMessage(self, 'cleared')
400        self.redirect(self.url(self.context, '@@manage')+'#tab2')
401        return
402
403class BedManageFormPage(KofaEditFormPage):
404    """ View to edit bed data
405    """
406    grok.context(IBed)
407    grok.name('index')
408    grok.require('waeup.manageHostels')
409    form_fields = grok.AutoFields(IBed).omit(
410        'bed_id', 'bed_number', 'bed_type')
411    label = _('Allocate student')
412    pnav = 5
413    doclink = DOCLINK + '/hostels.html#manage-bed'
414
415    @action(_('Save'))
416    def save(self, **data):
417        if data['owner'] == NOT_OCCUPIED:
418            self.flash(_('No valid student id.'), type='warning')
419            self.redirect(self.url(self.context))
420            return
421        msave(self, **data)
422        self.redirect(self.url(self.context.__parent__, '@@manage')+'#tab2')
423        return
424
425    def update(self):
426        if self.context.owner != NOT_OCCUPIED:
427            # Don't use this form for exchanging students.
428            # Beds must be released first before they can be allocated to
429            # other students.
430            self.redirect(self.url(self.context.__parent__, '@@manage')+'#tab2')
431        return
Note: See TracBrowser for help on using the repository browser.