source: indico/indico/MaKaC/plugins/Collaboration/base.py @ e11c57

burotelhello-world-walkthroughipv6new-webexv0.97-seriesv0.98-seriesv0.98.2v0.98.3v0.98b1v0.98b2v0.99v1.0v1.1
Last change on this file since e11c57 was e11c57, checked in by Pedro Ferreira <jose.pedro.ferreira@…>, 3 years ago

[IMP] Added getUniqueId() method to CSBookingBase

  • Property mode set to 100644
File size: 74.9 KB
Line 
1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20
21from persistent import Persistent
22from MaKaC.common.Counter import Counter
23from MaKaC.common.utils import formatDateTime, parseDateTime
24from MaKaC.common.timezoneUtils import getAdjustedDate, setAdjustedDate,\
25    datetimeToUnixTimeInt
26from MaKaC.webinterface import wcomponents, urlHandlers
27from MaKaC.plugins.base import PluginsHolder
28from MaKaC.errors import MaKaCError
29from MaKaC.services.interface.rpc.common import ServiceError
30from MaKaC.common.timezoneUtils import nowutc
31from MaKaC.common.logger import Logger
32from MaKaC.common.indexes import IndexesHolder
33from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools,\
34    MailTools
35from MaKaC.conference import Observer
36from MaKaC.common.contextManager import ContextManager
37from MaKaC.webinterface.common.tools import hasTags
38from MaKaC.plugins.Collaboration import mail
39from MaKaC.common.mail import GenericMailer
40import os, inspect
41import MaKaC.plugins.Collaboration as Collaboration
42from MaKaC.i18n import _
43from MaKaC.common.fossilize import Fossilizable, fossilizes
44from MaKaC.plugins.Collaboration.fossils import ICSErrorBaseFossil, ICSSanitizationErrorFossil,\
45    ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil
46
47
48class CSBookingManager(Persistent, Observer):
49    """ Class for managing the bookins of a meeting.
50        It will store the list of bookings. Adding / removing / editing bookings should be through this class.
51    """
52
53    _shouldBeTitleNotified = True
54    _shouldBeDateChangeNotified = True
55    _shouldBeDeletionNotified = True
56
57    def __init__(self, conf):
58        """ Constructor for the CSBookingManager class.
59            conf: a Conference object. The meeting that owns this CSBookingManager.
60        """
61        self._conf = conf
62        self._counter = Counter(1)
63        # a dict where the bookings will be stored. The key will be the booking id, the value a CSBookingBase object.
64        self._bookings = {}
65
66        # an index of bookings by type. The key will be a booking type (string), the value a list of booking id
67        self._bookingsByType = {}
68
69        # a list of ids with hidden bookings
70        self._hiddenBookings = set()
71
72        # an index of video services managers for each plugin. key: plugin name, value: list of users
73        self._managers = {}
74
75        #we register as an observer of the conference
76        self._conf.addObserver(self)
77
78    def getOwner(self):
79        """ Returns the Conference (the meeting) that owns this CSBookingManager object.
80        """
81        return self._conf
82
83    def restoreAsObserver(self):
84        if not self in self._conf.getObservers():
85            self._conf.addObserver(self)
86
87    def isCSAllowed(self, user = None):
88        """ Returns if the associated event should display a Video Services tab
89            This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
90            If a user is provided, we will take into account if the user can manage the plugin (for example,
91            an event manager cannot manage an admin-only plugin)
92        """
93        pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
94        if pluginsPerEventType:
95            for plugin in pluginsPerEventType[self._conf.getType()]:
96                if plugin.isActive() and (user is None or CollaborationTools.canUserManagePlugin(self._conf, plugin, user)):
97                    return True
98        return False
99
100    def getAllowedPlugins(self):
101        """ Returns a list of allowed plugins (Plugin objects) for this event.
102            Only active plugins are returned.
103            This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
104        """
105        pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
106        if pluginsPerEventType is not None:
107            allowedForThisEvent = pluginsPerEventType[self._conf.getType()]
108            return [plugin for plugin in allowedForThisEvent if plugin.isActive()]
109
110
111    def getBookingList(self, sorted = False, filterByType = None, notify = False, onlyPublic = False):
112        """ Returns a list of all the bookings.
113            If sorted = True, the list of bookings will be sorted by id.
114            If filterByType = None, all bookings are returned.
115            Otherwise, just those of the type "filterByType" if filterByType is a string,
116            or if it is a list of strings, those who have a type included in filterByType.
117        """
118        if not hasattr(self, "_bookingsByType"): #TODO: remove when safe
119            self._bookingsByType = {}
120
121        if filterByType is not None:
122            if type(filterByType) == str:
123                keys = self._bookingsByType.get(filterByType, [])
124            if type(filterByType) == list:
125                keys = []
126                for pluginName in filterByType:
127                    keys.extend(self._bookingsByType.get(pluginName, []))
128        else:
129            keys = self._bookings.keys()
130
131        if onlyPublic and self.getHiddenBookings():
132            keys = set(keys)
133            keys = keys.difference(self.getHiddenBookings())
134            keys = list(keys)
135
136        if sorted:
137            keys.sort(key = lambda k: int(k))
138
139        bookingList = [self._bookings[k] for k in keys]
140
141        #we notify all the bookings that they have been viewed. If a booking doesn't need to be viewed, nothing will happen
142        if notify:
143            for booking in bookingList:
144                if booking.needsToBeNotifiedOnView():
145                    try:
146                        booking._notifyOnView()
147                    except Exception, e:
148                        Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
149
150        return bookingList
151
152    def getBooking(self, id):
153        """ Returns a booking given its id.
154        """
155        return self._bookings[id]
156
157    def getSingleBooking(self, type, notify = False):
158        """ Returns the single booking of a plugin who only allows one booking.
159            type: a string with the name of the plugin
160            If the plugin actually allows multiple bookings, an exception will be thrown
161            If the plugin has no booking, None will be returned.
162            Otherwise the booking will be returned
163        """
164        if CollaborationTools.getCSBookingClass(type)._allowMultiple:
165            raise CollaborationException("Plugin type " + str(type) + " is not a single-booking plugin")
166        blist = self._bookingsByType.get(type,[])
167        if blist:
168            booking = self._bookings[blist[0]]
169            if notify:
170                try:
171                    booking._notifyOnView()
172                except Exception, e:
173                    Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
174            return booking
175        else:
176            return None
177
178    def getHiddenBookings(self):
179        if not hasattr(self, '_hiddenBookings'):
180            self._hiddenBookings = set()
181        return self._hiddenBookings
182
183    def hasBookings(self):
184        return len(self._bookings) > 0
185
186    def canCreateBooking(self, type):
187        """ Returns if it's possible to create a booking of this given type
188        """
189        if not CollaborationTools.getCSBookingClass(type)._allowMultiple:
190            return len(self.getBookingList(filterByType = type)) == 0
191        return True
192
193    def createBooking(self, bookingType, bookingParams = {}):
194        """ Adds a new booking to the list of bookings.
195            The id of the new booking is auto-generated incrementally.
196            After generating the booking, its "performBooking" method will be called.
197
198            bookingType: a String with the booking's plugin. Example: "DummyPlugin", "EVO"
199            bookingParams: a dictionary with the parameters necessary to create the booking.
200                           "create the booking" usually means Indico deciding if the booking can take place.
201                           if "startDate" and "endDate" are among the keys, they will be taken out of the dictionary.
202        """
203        if self.canCreateBooking(bookingType):
204            newBooking = CollaborationTools.getCSBookingClass(bookingType)(bookingType, self._conf)
205
206            error = newBooking.setBookingParams(bookingParams)
207
208            if isinstance(error, CSErrorBase):
209                return error
210            elif error:
211                raise CollaborationServiceException("Problem while creating a booking of type " + bookingType)
212            else:
213                newId = self._getNewBookingId()
214                newBooking.setId(newId)
215                createResult = newBooking._create()
216                if isinstance(createResult, CSErrorBase):
217                    return createResult
218                else:
219                    self._bookings[newId] = newBooking
220                    self._bookingsByType.setdefault(bookingType,[]).append(newId)
221                    if newBooking.isHidden():
222                        self.getHiddenBookings().add(newId)
223                    self._indexBooking(newBooking)
224                    self._notifyModification()
225                    self._sendMail(newBooking, 'new')
226                    return newBooking
227        else:
228            #we raise an exception because the web interface should take care of this never actually happening
229            raise CollaborationServiceException(bookingType + " only allows to create 1 booking per event")
230
231    def _indexBooking(self, booking):
232        if booking.shouldBeIndexed():
233            indexes = self._getIndexList(booking)
234            for index in indexes:
235                index.indexBooking(booking)
236
237    def changeBooking(self, bookingId, bookingParams):
238        """
239        Changes the bookingParams of a CSBookingBase object.
240        After updating the booking, its 'performBooking' method will be called.
241        bookingId: the id of the CSBookingBase object to change
242        bookingParams: a dictionary with the new parameters that will modify the booking
243        'modify the booking' can mean that maybe the booking will be rejected with the new parameters.
244        if 'startDate' and 'endDate' are among the keys, they will be taken out of the dictionary.
245        """
246
247        booking = self.getBooking(bookingId)
248
249
250        oldStartDate = booking.getStartDate()
251        oldModificationDate = booking.getModificationDate()
252        oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
253
254        error = booking.setBookingParams(bookingParams)
255        if isinstance(error, CSSanitizationError):
256            return error
257        elif error:
258            CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
259            if isinstance(error, CSErrorBase):
260                return error
261            raise CollaborationServiceException("Problem while modifying a booking of type " + booking.getType())
262        else:
263            modifyResult = booking._modify(oldBookingParams)
264            if isinstance(modifyResult, CSErrorBase):
265                CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
266                return modifyResult
267            else:
268                modificationDate = nowutc()
269                booking.setModificationDate(modificationDate)
270
271                if booking.isHidden():
272                    self.getHiddenBookings().add(booking.getId())
273                elif booking.getId() in self.getHiddenBookings():
274                    self.getHiddenBookings().remove(booking.getId())
275
276                self._changeStartDateInIndex(booking, oldStartDate, booking.getStartDate())
277                self._changeModificationDateInIndex(booking, oldModificationDate, modificationDate)
278
279                if booking.hasAcceptReject():
280                    if booking.getAcceptRejectStatus() is not None:
281                        booking.clearAcceptRejectStatus()
282                        self._addToPendingIndex(booking)
283
284                self._notifyModification()
285
286                self._sendMail(booking, 'modify')
287                return booking
288
289    @classmethod
290    def _rollbackChanges(cls, booking, oldBookingParams, oldModificationDate):
291        booking.setBookingParams(oldBookingParams)
292        booking.setModificationDate(oldModificationDate)
293
294    def _changeConfTitleInIndex(self, booking, oldTitle, newTitle):
295        if booking.shouldBeIndexed():
296            indexes = self._getIndexList(booking)
297            for index in indexes:
298                index.changeEventTitle(booking, oldTitle, newTitle)
299
300    def _changeStartDateInIndex(self, booking, oldStartDate, newStartDate):
301        if booking.shouldBeIndexed() and booking.hasStartDate():
302            indexes = self._getIndexList(booking)
303            for index in indexes:
304                index.changeStartDate(booking, oldStartDate, newStartDate)
305
306    def _changeModificationDateInIndex(self, booking, oldModificationDate, newModificationDate):
307        if booking.shouldBeIndexed():
308            indexes = self._getIndexList(booking)
309            for index in indexes:
310                index.changeModificationDate(booking, oldModificationDate, newModificationDate)
311
312    def _changeConfStartDateInIndex(self, booking, oldConfStartDate, newConfStartDate):
313        if booking.shouldBeIndexed():
314            indexes = self._getIndexList(booking)
315            for index in indexes:
316                index.changeConfStartDate(booking, oldConfStartDate, newConfStartDate)
317
318    def removeBooking(self, id):
319        """ Removes a booking given its id.
320        """
321        booking = self.getBooking(id)
322        bookingType = booking.getType()
323
324        removeResult = booking._delete()
325        if isinstance(removeResult, CSErrorBase):
326            return removeResult
327        else:
328            del self._bookings[id]
329            self._bookingsByType[bookingType].remove(id)
330            if not self._bookingsByType[bookingType]:
331                del self._bookingsByType[bookingType]
332            if id in self.getHiddenBookings():
333                self.getHiddenBookings().remove(id)
334
335            self._unindexBooking(booking)
336
337            self._notifyModification()
338
339            self._sendMail(booking, 'remove')
340
341            return booking
342
343    def _unindexBooking(self, booking):
344        if booking.shouldBeIndexed():
345            indexes = self._getIndexList(booking)
346            for index in indexes:
347                index.unindexBooking(booking)
348
349    def startBooking(self, id):
350        booking = self._bookings[id]
351        if booking.canBeStarted():
352            booking._start()
353            return booking
354        else:
355            raise CollaborationException(_("Tried to start booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be started."))
356
357    def stopBooking(self, id):
358        booking = self._bookings[id]
359        if booking.canBeStopped():
360            booking._stop()
361            return booking
362        else:
363            raise CollaborationException(_("Tried to stop booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be stopped."))
364
365    def checkBookingStatus(self, id):
366        booking = self._bookings[id]
367        if booking.hasCheckStatus():
368            result = booking._checkStatus()
369            if isinstance(result, CSErrorBase):
370                return result
371            else:
372                return booking
373        else:
374            raise ServiceError(_("Tried to check status of booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking does not support the check status service."))
375
376    def acceptBooking(self, id):
377        booking = self._bookings[id]
378        if booking.hasAcceptReject():
379            if booking.getAcceptRejectStatus() is None:
380                self._removeFromPendingIndex(booking)
381            booking.accept()
382            return booking
383        else:
384            raise ServiceError(_("Tried to accept booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be accepted."))
385
386    def rejectBooking(self, id, reason):
387        booking = self._bookings[id]
388        if booking.hasAcceptReject():
389            if booking.getAcceptRejectStatus() is None:
390                self._removeFromPendingIndex(booking)
391            booking.reject(reason)
392            return booking
393        else:
394            raise ServiceError("ERR-COLL10", _("Tried to reject booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be rejected."))
395
396    def _addToPendingIndex(self, booking):
397        if booking.shouldBeIndexed():
398            indexes = self._getPendingIndexList(booking)
399            for index in indexes:
400                index.indexBooking(booking)
401
402    def _removeFromPendingIndex(self, booking):
403        if booking.shouldBeIndexed():
404            indexes = self._getPendingIndexList(booking)
405            for index in indexes:
406                index.unindexBooking(booking)
407
408    def _getNewBookingId(self):
409        return self._counter.newCount()
410
411    def _getIndexList(self, booking):
412        """ Returns a list of BookingsIndex objects where the booking should be indexed.
413            This list includes:
414            -an index of all bookings
415            -an index of bookings of the given type
416            -an index of all bookings in the category of the event
417            -an index of booking of the given type, in the category of the event
418            If the booking type declared common indexes:
419            -the common indexes
420            -the common indexes for the category of the event
421            If the booking is of the Accept/Reject type
422            -same indexes as above, but only for pending bookings
423        """
424        collaborationIndex = IndexesHolder().getById("collaboration")
425        indexes = [collaborationIndex.getAllBookingsIndex(),
426                   collaborationIndex.getIndex(booking.getType())]
427
428        for commonIndexName in booking.getCommonIndexes():
429            indexes.append(collaborationIndex.getIndex(commonIndexName))
430
431        if booking.hasAcceptReject() and booking.getAcceptRejectStatus() is None:
432            indexes.extend(self._getPendingIndexList(booking))
433
434        return indexes
435
436    def _getPendingIndexList(self, booking):
437        collaborationIndex = IndexesHolder().getById("collaboration")
438        indexes = [collaborationIndex.getIndex("all_pending"),
439                   collaborationIndex.getIndex(booking.getType() + "_pending")]
440
441        for commonIndexName in booking.getCommonIndexes():
442            indexes.append(collaborationIndex.getIndex(commonIndexName + "_pending"))
443
444        return indexes
445
446    def _sendMail(self, booking, operation):
447        if MailTools.needToSendEmails():
448
449            if operation == 'new':
450                try:
451                    notification = mail.NewBookingNotification(booking)
452                    GenericMailer.sendAndLog(notification, self._conf,
453                                         "MaKaC/plugins/Collaboration/base.py",
454                                         self._conf.getCreator())
455                except Exception, e:
456                    Logger.get('VideoServ').error(
457                        """Could not send NewBookingNotification for booking with id %s of event with id %s, exception: %s""" %
458                        (booking.getId(), self._conf.getId(), str(e)))
459                    raise e
460
461            elif operation == 'modify':
462                try:
463                    notification = mail.BookingModifiedNotification(booking)
464                    GenericMailer.sendAndLog(notification, self._conf,
465                                         "MaKaC/plugins/Collaboration/base.py",
466                                         self._conf.getCreator())
467                except Exception, e:
468                    Logger.get('VideoServ').error(
469                        """Could not send BookingModifiedNotification for booking with id %s of event with id %s, exception: %s""" %
470                        (booking.getId(), self._conf.getId(), str(e)))
471                    raise e
472
473            elif operation == 'remove':
474                try:
475                    notification = mail.BookingDeletedNotification(booking)
476                    GenericMailer.sendAndLog(notification, self._conf,
477                                         "MaKaC/plugins/Collaboration/base.py",
478                                         self._conf.getCreator())
479                except Exception, e:
480                    Logger.get('VideoServ').error(
481                        """Could not send BookingDeletedNotification for booking with id %s of event with id %s, exception: %s""" %
482                        (booking.getId(), self._conf.getId(), str(e)))
483                    raise e
484
485    def getManagers(self):
486        if not hasattr(self, "_managers"):
487            self._managers = {}
488        return self._managers
489
490    def addPluginManager(self, plugin, user):
491        #TODO: use .linkTo on the user. To be done when the list of roles of a user is actually needed for smth...
492        self.getManagers().setdefault(plugin, []).append(user)
493        self._notifyModification()
494
495    def removePluginManager(self, plugin, user):
496        #TODO: use .unlinkTo on the user. To be done when the list of roles of a user is actually needed for smth...
497        if user in self.getManagers().setdefault(plugin,[]):
498            self.getManagers()[plugin].remove(user)
499            self._notifyModification()
500
501    def getVideoServicesManagers(self):
502        return self.getManagers().setdefault('all', [])
503
504    def isVideoServicesManager(self, user):
505        return user in self.getManagers().setdefault('all', [])
506
507    def getPluginManagers(self, plugin):
508        return self.getManagers().setdefault(plugin, [])
509
510    def isPluginManager(self, plugin, user):
511        return user in self.getManagers().setdefault(plugin, [])
512
513    def getAllManagers(self):
514        """ Returns a list with all the managers, no matter their type
515            The returned list is not ordered.
516        """
517        managers = set()
518        for managerList in self.getManagers().itervalues():
519            managers = managers.union(managerList)
520        return list(managers)
521
522    def isPluginManagerOfAnyPlugin(self, user):
523        #TODO: this method is not optimal. to be optimal, we should store somewhere an index where the key
524        #is the user, and the value is a list of plugins where they are managers.
525        #this could be done with .getLinkTo, but we would need to change the .linkTo method to add extra information
526        #(since we cannot create a role for each plugin)
527        if self.isVideoServicesManager(user):
528            return True
529        else:
530            for plugin in self.getManagers().iterkeys():
531                if self.isPluginManager(plugin, user):
532                    return True
533            return False
534
535    def notifyTitleChange(self, oldTitle, newTitle):
536        """ Notifies the CSBookingManager that the title of the event (meeting) it's attached to has changed.
537            The CSBookingManager will reindex all its bookings in the event title index.
538            This method will be called by the event (meeting) object
539        """
540        for booking in self.getBookingList():
541            try:
542                self._changeConfTitleInIndex(booking, oldTitle, newTitle)
543            except Exception, e:
544                Logger.get('VideoServ').exception("Exception while reindexing a booking in the event title index because its event's title changed: " + str(e))
545
546
547    def notifyEventDateChanges(self, oldStartDate = None, newStartDate = None, oldEndDate = None, newEndDate = None):
548        """ Notifies the CSBookingManager that the start and / or end dates of the event it's attached to have changed.
549            The CSBookingManager will change the dates of all the bookings that want to be updated.
550            If there are problems (such as a booking not being able to be modified)
551            it will write a list of strings describing the problems in the 'dateChangeNotificationProblems' context variable.
552            (each string is produced by the _booking2NotifyProblem method).
553            This method will be called by the event (meeting) object.
554        """
555        startDateChanged = oldStartDate is not None and newStartDate is not None and not oldStartDate == newStartDate
556        endDateChanged = oldEndDate is not None and newEndDate is not None and not oldEndDate == newEndDate
557        someDateChanged = startDateChanged or endDateChanged
558
559        Logger.get("VideoServ").info("""CSBookingManager: starting notifyEventDateChanges. Arguments: confId=%s, oldStartDate=%s, newStartDate=%s, oldEndDate=%s, newEndDate=%s""" %
560                                     (str(self._conf.getId()), str(oldStartDate), str(newStartDate), str(oldEndDate), str(newEndDate)))
561
562        if someDateChanged:
563            problems = []
564            for booking in self.getBookingList():
565                if booking.hasStartDate():
566                    if startDateChanged:
567                        try:
568                            self._changeConfStartDateInIndex(booking, oldStartDate, newStartDate)
569                        except Exception, e:
570                            Logger.get('VideoServ').error("Exception while reindexing a booking in the event start date index because its event's start date changed: " + str(e))
571
572                    if booking.needsToBeNotifiedOfDateChanges():
573                        Logger.get("VideoServ").info("""CSBookingManager: notifying date changes to booking %s of event %s""" %
574                                                     (str(booking.getId()), str(self._conf.getId())))
575                        oldBookingStartDate = booking.getStartDate()
576                        oldBookingEndDate = booking.getEndDate()
577                        oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
578
579                        if startDateChanged:
580                            booking.setStartDate(oldBookingStartDate + (newStartDate - oldStartDate) )
581                        if endDateChanged:
582                            booking.setEndDate(oldBookingEndDate + (newEndDate - oldEndDate) )
583
584                        rollback = False
585                        modifyResult = None
586                        try:
587                            modifyResult = booking._modify(oldBookingParams)
588                            if isinstance(modifyResult, CSErrorBase):
589                                Logger.get('VideoServ').warning("""Error while changing the dates of booking %s of event %s after event dates changed: %s""" %
590                                                                (str(booking.getId()), str(self._conf.getId()), modifyResult.getLogMessage()))
591                                rollback = True
592                        except Exception, e:
593                            Logger.get('VideoServ').error("""Exception while changing the dates of booking %s of event %s after event dates changed: %s""" %
594                                                          (str(booking.getId()), str(self._conf.getId()), str(e)))
595                            rollback = True
596
597                        if rollback:
598                            booking.setStartDate(oldBookingStartDate)
599                            booking.setEndDate(oldBookingEndDate)
600                            problems.append(CSBookingManager._booking2NotifyProblem(booking, modifyResult))
601                        elif startDateChanged:
602                            self._changeStartDateInIndex(booking, oldBookingStartDate, booking.getStartDate())
603
604                if hasattr(booking, "notifyEventDateChanges"):
605                    try:
606                        booking.notifyEventDateChanges(oldStartDate, newStartDate, oldEndDate, newEndDate)
607                    except Exception, e:
608                        Logger.get('VideoServ').exception("Exception while notifying a plugin of an event date changed: " + str(e))
609
610            if problems:
611                ContextManager.get('dateChangeNotificationProblems')['Collaboration'] = [
612                    'Some Video Services bookings could not be moved:',
613                    problems,
614                    'Go to [[' + str(urlHandlers.UHConfModifCollaboration.getURL(self.getOwner(), secure = CollaborationTools.isUsingHTTPS())) + ' the Video Services section]] to modify them yourself.'
615                ]
616
617
618    def notifyTimezoneChange(self, oldTimezone, newTimezone):
619        """ Notifies the CSBookingManager that the timezone of the event it's attached to has changed.
620            The CSBookingManager will change the dates of all the bookings that want to be updated.
621            This method will be called by the event (Conference) object
622        """
623        return []
624
625    @classmethod
626    def _booking2NotifyProblem(cls, booking, modifyError):
627        """ Turns a booking into a string used to tell the user
628            why a date change of a booking triggered by the event's start or end date change
629            went bad.
630        """
631
632        message = []
633        message.extend(["The dates of the ", booking.getType(), " booking"])
634        if booking.hasTitle():
635            message.extend([': "', booking._getTitle(), '" (', booking.getStartDateAsString(), ' - ', booking.getEndDateAsString(), ')'])
636        else:
637            message.extend([' ongoing from ', booking.getStartDateAsString(), ' to ', booking.getEndDateAsString(), ''])
638
639        message.append(' could not be changed.')
640        if modifyError and modifyError.getUserMessage():
641            message.extend([' Reason: ', modifyError.getUserMessage()])
642        return "".join(message)
643
644
645    def notifyDeletion(self):
646        """ Notifies the CSBookingManager that the Conference object it is attached to has been deleted.
647            The CSBookingManager will change the dates of all the bookings that want to be updated.
648            This method will be called by the event (Conference) object
649        """
650        for booking in self.getBookingList():
651            try:
652                removeResult = booking._delete()
653                if isinstance(removeResult, CSErrorBase):
654                    Logger.get('VideoServ').warning("Error while deleting a booking of type %s after deleting an event: %s"%(booking.getType(), removeResult.getLogMessage() ))
655                self._unindexBooking(booking)
656            except Exception, e:
657                Logger.get('VideoServ').exception("Exception while deleting a booking of type %s after deleting an event: %s" % (booking.getType(), str(e)))
658
659
660    def getEventDisplayPlugins(self, sorted = False):
661        """ Returns a list of names (strings) of plugins which have been configured
662            as showing bookings in the event display page, and which have bookings
663            already (or previously) created in the event.
664            (does not check if the bookings are hidden or not)
665        """
666
667        pluginsWithEventDisplay = CollaborationTools.pluginsWithEventDisplay()
668        l = []
669        for pluginName in self._bookingsByType:
670            if pluginName in pluginsWithEventDisplay:
671                l.append(pluginName)
672        if sorted:
673            l.sort()
674        return l
675
676    def createTestBooking(self, bookingParams = {}):
677        """ Function that creates a 'test' booking for performance test.
678            Avoids to use any of the plugins except DummyPlugin
679        """
680        from MaKaC.plugins.Collaboration.DummyPlugin.collaboration import CSBooking as DummyBooking
681        bookingType = 'DummyPlugin'
682        newBooking = DummyBooking(bookingType, self._conf)
683        error = newBooking.setBookingParams(bookingParams)
684        if error:
685            raise CollaborationServiceException("Problem while creating a test booking")
686        else:
687            newId = self._getNewBookingId()
688            newBooking.setId(newId)
689            createResult = newBooking._create()
690            if isinstance(createResult, CSErrorBase):
691                return createResult
692            else:
693                self._bookings[newId] = newBooking
694                self._bookingsByType.setdefault(bookingType,[]).append(newId)
695                if newBooking.isHidden():
696                    self.getHiddenBookings().add(newId)
697                self._indexBooking(newBooking)
698                self._notifyModification()
699                return newBooking
700
701    def _notifyModification(self):
702        self._p_changed = 1
703
704
705
706class CSBookingBase(Persistent, Fossilizable):
707    fossilizes(ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil)
708
709    """ Base class that represents a Collaboration Systems booking.
710        Every Collaboration plugin will have to implement this class.
711        In the base class are gathered all the functionalities / elements that are common for all plugins.
712        A booking is Persistent (DateChangeObserver inherits from Persistent) so it will be stored in the database.
713        Also, every CSBookingBase object in the server will be mirrored by a Javascript object in the client, through "Pickling".
714
715        Every class that implements the CSBookingBase has to declare the following class attributes:
716            _hasStart : True if the plugin has a "start" concept. Otherwise, the "start" button will not appear, etc.
717            _hasStop : True if the plugin has a "stop" concept. Otherwise, the "stop" button will not appear, etc.
718            _hasCheckStatus: True if the plugin has a "check status" concept. Otherwise, the "check status" button will not appear, etc.
719            _hasAcceptReject: True if the plugin has a "accept or reject" concept. Otherwise, the "accept" and "reject" buttons will not appear, etc.
720            _requiresServerCallForStart : True if we should notify the server when the user presses the "start" button.
721            _requiresServerCallForStop : True if we should notify the server when the user presses the "stop" button.
722            _requiresClientCallForStart : True if the browser should execute some JS action when the user presses the "start" button.
723            _requiresClientCallForStop : True if the browser should execute some JS action when the user presses the "stop" button.
724            _needsBookingParamsCheck : True if the booking parameters should be checked after the booking is added / edited.
725                                       If True, the _checkBookingParams method will be called by the setBookingParams method.
726            _needsToBeNotifiedOnView: True if the booking object needs to be notified (through the "notifyOnView" method)
727                                      when the user "sees" the booking, for example when returning the list of bookings.
728            _canBeNotifiedOfEventDateChanges: True if bookings of this type should be able to be notified
729                                              of their owner Event changing start date, end date or timezone.
730            _allowMultiple: True if this booking type allows more than 1 booking per event.
731    """
732
733    _hasStart = False
734    _hasStop = False
735    _hasCheckStatus = False
736    _hasAcceptReject = False
737    _hasStartStopAll = False
738    _requiresServerCallForStart = False
739    _requiresServerCallForStop = False
740    _requiresClientCallForStart = False
741    _requiresClientCallForStop = False
742    _needsBookingParamsCheck = False
743    _needsToBeNotifiedOnView = False
744    _canBeNotifiedOfEventDateChanges = True
745    _allowMultiple = True
746    _shouldBeIndexed = True
747    _commonIndexes = []
748    _hasStartDate = True
749    _hasEventDisplay = False
750    _hasTitle = False
751    _adminOnly = False
752
753    def __init__(self, bookingType, conf):
754        """ Constructor for the CSBookingBase class.
755            id: a string with the id of the booking
756            bookingType: a string with the type of the booking. Example: "DummyPlugin", "EVO"
757            conf: a Conference object to which this booking belongs (through the CSBookingManager object). The meeting of this booking.
758            startTime: TODO
759            endTime: TODO
760
761            Other attributes initialized by this constructor:
762            -_bookingParams: the parameters necessary to perform the booking.
763                             The plugins will decide if the booking gets authorized or not depending on this.
764                             Needs to be defined by the implementing class, as keys with empty values.
765            -_startingParams: the parameters necessary to start the booking.
766                              They will be used on the client for the local start action.
767                              Needs to be defined by the implementing class, as keys with empty values.
768            -_statusMessage, _statusClass : they represent the status message (and its CSS class) that will be displayed.
769                 The status of a booking can be, for example: "Booking Accepted" (in green), "Booking refused" (in red)
770            -_warning: A warning is a plugin-defined object, with information to show to the user when
771                       the operation went well but we still have to show some info to the user.
772            -_canBeStarted: If its value is true, the "start" button for the booking will be able to be pushed.
773                            It can be false if, for example:
774                              + The plugin didn't like the booking parameters and doesn't give permission for the booking to be started,
775                              + The booking has already been started, so the "start" button has to be faded in order not to be pressed twice.
776            -_canBeStopped: If its value is true, the "stop" button for the booking will be able to be pushed.
777                            For example, before starting a booking the "stop" button for the booking will be faded.
778            -_permissionToStart : Even if the "start" button for a booking is able to be pushed, there may be cases where the booking should
779                            not start. For example, if it's not the correct time yet.
780                            In that case "permissionToStart" should be set to false so that the booking doesn't start.
781            -_permissionToStop: Same as permissionToStart. Sometimes the booking should not be allowed to stop even if the "stop" button is available.
782        """
783        self._id = None
784        self._type = bookingType
785        self._plugin = CollaborationTools.getPlugin(self._type)
786        self._conf = conf
787        self._warning = None
788        self._creationDate = nowutc()
789        self._modificationDate = nowutc()
790        self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
791        self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
792        self._startDate = None
793        self._endDate = None
794        self._startDateTimestamp = None
795        self._endDateTimestamp = None
796        self._statusMessage = ""
797        self._statusClass = ""
798        self._acceptRejectStatus = None #None = not yet accepted / rejected; True = accepted; False = rejected
799        self._rejectReason = ""
800        self._bookingParams = {}
801        self._canBeDeleted = True
802        self._canBeStarted = self._hasStart
803        self._canBeStopped = False
804        self._permissionToStart = False
805        self._permissionToStop = False
806        self._needsToBeNotifiedOfDateChanges = self._canBeNotifiedOfEventDateChanges
807        self._hidden = False
808
809        setattr(self, "_" + bookingType + "Options", CollaborationTools.getPlugin(bookingType).getOptions())
810
811
812    def getId(self):
813        """ Returns the internal, per-conference id of the booking.
814            This attribute will be available in Javascript with the "id" identifier.
815        """
816        return self._id
817
818    def setId(self, id):
819        """ Sets the internal, per-conference id of the booking
820        """
821        self._id = id
822
823    def getUniqueId(self):
824        """ Returns an unique Id that identifies this booking server-wide.
825            Useful for ExternalOperationsManager
826        """
827        return "%scsbook%s" % (self.getConference().getUniqueId(), self.getId())
828
829    def getType(self):
830        """ Returns the type of the booking, as a string: "EVO", "DummyPlugin"
831            This attribute will be available in Javascript with the "type" identifier.
832        """
833        return self._type
834
835    def getConference(self):
836        """ Returns the owner of this CSBookingBase object, which is a Conference object representing the meeting.
837        """
838        return self._conf
839
840    def getWarning(self):
841        """ Returns a warning attached to this booking.
842            A warning is a plugin-defined object, with information to show to the user when
843            the operation went well but we still have to show some info to the user.
844            To be overloaded by plugins.
845        """
846        if not hasattr(self, '_warning'):
847            self._warning = None
848        return self._warning
849
850    def getCreationDate(self):
851        """ Returns the date this booking was created, as a timezone localized datetime object
852        """
853        if not hasattr(self, "_creationDate"): #TODO: remove when safe
854            self._creationDate = nowutc()
855        return self._creationDate
856
857    def getAdjustedCreationDate(self, tz=None):
858        """ Returns the booking creation date, adjusted to a given timezone.
859            If no timezone is provided, the event's timezone is used
860        """
861        return getAdjustedDate(self.getCreationDate(), self.getConference(), tz)
862
863    def getCreationDateTimestamp(self):
864        if not hasattr(object, "_creationDateTimestamp"): #TODO: remove when safe
865            self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
866        return self._creationDateTimestamp
867
868    def getModificationDate(self):
869        """ Returns the date this booking was modified last
870        """
871        if not hasattr(self, "_modificationDate"):  #TODO: remove when safe
872            self._modificationDate = nowutc()
873        return self._modificationDate
874
875    def getAdjustedModificationDate(self, tz=None):
876        """ Returns the booking last modification date, adjusted to a given timezone.
877            If no timezone is provided, the event's timezone is used
878        """
879        return getAdjustedDate(self.getModificationDate(), self.getConference(), tz)
880
881    def getModificationDateTimestamp(self):
882        if not hasattr(object, "_modificationDateTimestamp"): #TODO: remove when safe
883            self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
884        return self._modificationDateTimestamp
885
886    def setModificationDate(self, date):
887        """ Sets the date this booking was modified last
888        """
889        self._modificationDate = date
890        if date:
891            self._modificationDateTimestamp = int(datetimeToUnixTimeInt(date))
892        else:
893            self._modificationDateTimestamp = None
894
895    def getBookingsOfSameType(self, sorted = False):
896        """ Returns a list of the bookings of the same type as this one (including this one)
897            sorted: if true, bookings will be sorted by id
898        """
899        return self._conf.getCSBookingManager().getBookingList(sorted, self._type)
900
901    def getPlugin(self):
902        """ Returns the Plugin object associated to this booking.
903        """
904        return self._plugin
905
906    def getPluginOptions(self):
907        """ Utility method that returns the plugin options for this booking's type of plugin
908        """
909        return self._plugin.getOptions()
910
911    def getPluginOptionByName(self, optionName):
912        """ Utility method that returns a plugin option, given its name, for this booking's type of plugin
913        """
914        return self.getPluginOptions()[optionName]
915
916    def getStartDate(self):
917        """ Returns the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
918        """
919        return self._startDate
920
921    def getAdjustedStartDate(self, tz=None):
922        """ Returns the booking start date, adjusted to a given timezone.
923            If no timezone is provided, the event's timezone is used
924        """
925        if self.getStartDate():
926            return getAdjustedDate(self.getStartDate(), self.getConference(), tz)
927        else:
928            return None
929
930    def getStartDateTimestamp(self):
931        if not hasattr(object, "_startDateTimestamp"): #TODO: remove when safe
932            self._startDateTimestamp = int(datetimeToUnixTimeInt(self._startDate))
933        return self._startDateTimestamp
934
935    def getStartDateAsString(self):
936        """ Returns the start date as a string, expressed in the meeting's timezone
937        """
938        if self._startDate == None:
939            return ""
940        else:
941            return formatDateTime(self.getAdjustedStartDate())
942
943    def setStartDate(self, startDate):
944        """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
945        """
946        self._startDate = startDate
947        if startDate:
948            self._startDateTimestamp = int(datetimeToUnixTimeInt(startDate))
949        else:
950            self._startDateTimestamp = None
951
952    def setStartDateFromString(self, startDateString):
953        """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
954        """
955        if startDateString == "":
956            self.setStartDate(None)
957        else:
958            try:
959                self.setStartDate(setAdjustedDate(parseDateTime(startDateString), self._conf))
960            except ValueError:
961                raise CollaborationServiceException("startDate parameter (" + startDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
962
963    def getEndDate(self):
964        """ Returns the end date as an datetime object with timezone information (adjusted to the meeting's timezone)
965        """
966        return self._endDate
967
968    def getAdjustedEndDate(self, tz=None):
969        """ Returns the booking end date, adjusted to a given timezone.
970            If no timezone is provided, the event's timezone is used
971        """
972        return getAdjustedDate(self.getEndDate(), self.getConference(), tz)
973
974    def getEndDateTimestamp(self):
975        if not hasattr(object, "_endDateTimestamp"): #TODO: remove when safe
976            self._endDateTimestamp = int(datetimeToUnixTimeInt(self._endDate))
977        return self._endDateTimestamp
978
979    def getEndDateAsString(self):
980        """ Returns the start date as a string, expressed in the meeting's timezone
981        """
982        if self._endDate == None:
983            return ""
984        else:
985            return formatDateTime(self.getAdjustedEndDate())
986
987    def setEndDate(self, endDate):
988        """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
989        """
990        self._endDate = endDate
991        if endDate:
992            self._endDateTimestamp = int(datetimeToUnixTimeInt(endDate))
993        else:
994            self._endDateTimestamp = None
995
996    def setEndDateFromString(self, endDateString):
997        """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
998        """
999        if endDateString == "":
1000            self.setEndDate(None)
1001        else:
1002            try:
1003                self.setEndDate(setAdjustedDate(parseDateTime(endDateString), self._conf))
1004            except ValueError:
1005                raise CollaborationServiceException("endDate parameter (" + endDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
1006
1007    def getStatusMessage(self):
1008        """ Returns the status message as a string.
1009            This attribute will be available in Javascript with the "statusMessage"
1010        """
1011        return self._statusMessage
1012
1013    def getStatusClass(self):
1014        """ Returns the status message CSS class as a string.
1015            This attribute will be available in Javascript with the "statusClass"
1016        """
1017        if not hasattr(self, "_statusClass"): #TODO: remove when safe
1018            self._statusClass = ""
1019        return self._statusClass
1020
1021    def accept(self):
1022        """ Sets this booking as accepted
1023        """
1024        self._acceptRejectStatus = True
1025        self._accept()
1026
1027    def reject(self, reason):
1028        """ Sets this booking as rejected, and stores the reason
1029        """
1030        self._acceptRejectStatus = False
1031        self._rejectReason = reason
1032        self._reject()
1033
1034    def clearAcceptRejectStatus(self):
1035        """ Sets back the accept / reject status to None
1036        """
1037        self._acceptRejectStatus = None
1038
1039    def getAcceptRejectStatus(self):
1040        """ Returns the Accept/Reject status of the booking
1041            This attribute will be available in Javascript with the "acceptRejectStatus"
1042            Its value will be:
1043            -None if the booking has not been accepted or rejected yet,
1044            -True if it has been accepted,
1045            -False if it has been rejected
1046        """
1047        if not hasattr(self, "_acceptRejectStatus"):
1048            self._acceptRejectStatus = None
1049        return self._acceptRejectStatus
1050
1051    def getRejectReason(self):
1052        """ Returns the rejection reason.
1053            This attribute will be available in Javascript with the "rejectReason"
1054        """
1055        if not hasattr(self, "_rejectReason"):
1056            self._rejectReason = ""
1057        return self._rejectReason
1058
1059    def getBookingParams(self):
1060        """ Returns a dictionary with the booking params.
1061            This attribute will be available in Javascript with the "bookingParams"
1062
1063            If self._bookingParams has not been set by the implementing class, an exception is thrown.
1064
1065            Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1066            be retrieved through getter methods.
1067            If a subclass defines a class attributes called _complexParameters (a list of strings),
1068            parameter names that are in  this list will also be included in the returned dictionary.
1069            Their value will be retrieved by calling the corresponding getXXX methods
1070            but instead the inheriting class's setXXX method will be called.
1071            Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] correspond
1072            to the methods getCommunityName, getAccessPassword, getHasAccessPassword.
1073            If you include a parameter in the _complexParameters list, you always have to implement the corresponding getter method.
1074        """
1075        bookingParams = {}
1076        for k, v in self.__class__._simpleParameters.iteritems():
1077            if k in self._bookingParams:
1078                value = self._bookingParams[k]
1079            else:
1080                value = v[1] #we use the default value
1081            if v[0] is bool and value is True: #we assume it will be used in a single checkbox
1082                value = ["yes"]
1083            if value is not False: #we do not include False, it means the single checkbox is not checked
1084                bookingParams[k] = value
1085
1086        if hasattr(self.__class__, "_complexParameters") and len(self.__class__._complexParameters) > 0:
1087            getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1088            for paramName in self.__class__._complexParameters:
1089                getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1090                if getMethodName in getterMethods:
1091                    bookingParams[paramName] = getterMethods[getMethodName]()
1092                else:
1093                    raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1094
1095        bookingParams["startDate"] = self.getStartDateAsString()
1096        bookingParams["endDate"] = self.getEndDateAsString()
1097        if self.needsToBeNotifiedOfDateChanges():
1098            bookingParams["notifyOnDateChanges"] = ["yes"]
1099        if self.isHidden():
1100            bookingParams["hidden"] = ["yes"]
1101
1102        return bookingParams
1103
1104
1105    def getBookingParamByName(self, paramName):
1106        if paramName in self.__class__._simpleParameters:
1107            if not paramName in self._bookingParams:
1108                self._bookingParams[paramName] = self.__class__._simpleParameters[paramName][1]
1109            return self._bookingParams[paramName]
1110        elif hasattr(self.__class__, "_complexParameters") and paramName in self.__class__._complexParameters:
1111            getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1112            getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1113            if getMethodName in getterMethods:
1114                return getterMethods[getMethodName]()
1115            else:
1116                raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1117        else:
1118            raise CollaborationServiceException("Tried to retrieve parameter " + str(paramName) + " but this parameter does not exist")
1119
1120
1121    def setBookingParams(self, params):
1122        """ Sets new booking parameters.
1123            params: a dict with key/value pairs with the new values for the booking parameters.
1124            If the plugin's _needsBookingParamsCheck is True, the _checkBookingParams() method will be called.
1125            This function will return False if all the checks were OK or if there were no checks, and otherwise will throw
1126            an exception or return a CSReturnedErrorBase error.
1127
1128            Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1129            be set through setter methods.
1130            If a subclass defines a class attributes called _complexParameters (a list of strings),
1131            parameter names that are in 'params' and also in this list will not be assigned directly,
1132            but instead the inheriting class's setXXX method will be called.
1133
1134            Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] corresponds
1135            to methods setCommunityName, setAccessPassword, setHasAccessPassword.
1136            Note: even if a parameter is in this list, you can decide not to implement its corresponding set
1137            method if you never expect the parameter name to come up inside 'params'.
1138        """
1139
1140        sanitizeResult = self.sanitizeParams(params)
1141        if sanitizeResult:
1142            return sanitizeResult
1143
1144        self.setHidden(params.pop("hidden", False) == ["yes"])
1145        self.setNeedsToBeNotifiedOfDateChanges(params.pop("notifyOnDateChanges", False) == ["yes"])
1146
1147        startDate = params.pop("startDate", None)
1148        if startDate is not None:
1149            self.setStartDateFromString(startDate)
1150        endDate = params.pop("endDate", None)
1151        if endDate is not None:
1152            self.setEndDateFromString(endDate)
1153
1154        for k,v in params.iteritems():
1155            if k in self.__class__._simpleParameters:
1156                if self.__class__._simpleParameters[k][0]:
1157                    try:
1158                        v = self.__class__._simpleParameters[k][0](v)
1159                    except ValueError:
1160                        raise CollaborationServiceException("Tried to set value of parameter with name " + str(k) + ", recognized as a simple parameter of type" + str(self._simpleParameters[k]) + ", but the conversion failed")
1161                self._bookingParams[k] = v
1162            elif k in self.__class__._complexParameters:
1163                setterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('set')))
1164                setMethodName = 'set' + k[0].upper() + k[1:]
1165                if setMethodName in setterMethods:
1166                    setterMethods[setMethodName](v)
1167                else:
1168                    raise CollaborationServiceException("Tried to set value of parameter with name " + str(k) + ", recognized as a complex parameter, but the corresponding setter method " + setMethodName + " is not implemented")
1169            else:
1170                raise CollaborationServiceException("Tried to set the value of a parameter with name " + str(k) + " that was not declared")
1171
1172        for k, v in self.__class__._simpleParameters.iteritems():
1173            if not k in self._bookingParams:
1174                self._bookingParams[k] = self.__class__._simpleParameters[k][1]
1175
1176        if self.needsBookingParamsCheck():
1177            return self._checkBookingParams()
1178
1179        return False
1180
1181    def sanitizeParams(self, params):
1182        """ Checks if the fields introduced into the booking / request form
1183            have any kind of HTML or script tag.
1184        """
1185        if not isinstance(params, dict):
1186            raise CollaborationServiceException("Booking parameters are not a dictionary")
1187
1188        invalidFields = []
1189        for k, v in params.iteritems():
1190            if type(v) == str and hasTags(v):
1191                invalidFields.append(k)
1192
1193        if invalidFields:
1194            return CSSanitizationError(invalidFields)
1195        else:
1196            return None
1197
1198    def _getTypeDisplayName(self):
1199        return CollaborationTools.getXMLGenerator(self._type).getDisplayName()
1200
1201    def _getFirstLineInfo(self, tz):
1202        return CollaborationTools.getXMLGenerator(self._type).getFirstLineInfo(self, tz)
1203
1204    def _getTitle(self):
1205        if self.hasEventDisplay():
1206            raise CollaborationException("Method _getTitle was not overriden for the plugin type " + str(self._type))
1207
1208    def _getInformationDisplay(self, tz):
1209        templateClass = CollaborationTools.getTemplateClass(self.getType(), "WInformationDisplay")
1210        if templateClass:
1211            return templateClass(self, tz).getHTML()
1212        else:
1213            return None
1214
1215    def _getLaunchDisplayInfo(self):
1216        """ To be overloaded by plugins
1217        """
1218        return None
1219
1220    def _checkBookingParams(self):
1221        """ To be overriden by inheriting classes.
1222            Verifies that the booking parameters are correct. For example, that a numeric field is actually a number.
1223            Otherwise, an exception should be thrown.
1224            If there are no errors, the method should just return.
1225        """
1226        if self.needsBookingParamsCheck():
1227            raise CollaborationServiceException("Method _checkBookingParams was not overriden for the plugin type " + str(self._type))
1228
1229    def hasStart(self):
1230        """ Returns if this booking belongs to a plugin who has a "start" concept.
1231            This attribute will be available in Javascript with the "hasStart" attribute
1232        """
1233        return self._hasStart
1234
1235    def hasStartStopAll(self):
1236        """ Returns if this booking belongs to a plugin who has a "start" concept, and all of its bookings for a conference
1237            can be started simultanously.
1238            This attribute will be available in Javascript with the "hasStart" attribute
1239        """
1240        return self._hasStartStopAll
1241
1242    def hasStop(self):
1243        """ Returns if this booking belongs to a plugin who has a "stop" concept.
1244            This attribute will be available in Javascript with the "hasStop" attribute
1245        """
1246        return self._hasStop
1247
1248    def hasCheckStatus(self):
1249        """ Returns if this booking belongs to a plugin who has a "check status" concept.
1250            This attribute will be available in Javascript with the "hasCheckStatus" attribute
1251        """
1252        return self._hasCheckStatus
1253
1254    def hasAcceptReject(self):
1255        """ Returns if this booking belongs to a plugin who has a "accept or reject" concept.
1256            This attribute will be available in Javascript with the "hasAcceptReject" attribute
1257        """
1258        return self._hasAcceptReject
1259
1260    def requiresServerCallForStart(self):
1261        """ Returns if this booking belongs to a plugin who requires a server call when the start button is pressed.
1262            This attribute will be available in Javascript with the "requiresServerCallForStart" attribute
1263        """
1264        return self._requiresServerCallForStart
1265
1266    def requiresServerCallForStop(self):
1267        """ Returns if this booking belongs to a plugin who requires a server call when the stop button is pressed.
1268            This attribute will be available in Javascript with the "requiresServerCallForStop" attribute
1269        """
1270        return self._requiresServerCallForStop
1271
1272    def requiresClientCallForStart(self):
1273        """ Returns if this booking belongs to a plugin who requires a client call when the start button is pressed.
1274            This attribute will be available in Javascript with the "requiresClientCallForStart" attribute
1275        """
1276        return self._requiresClientCallForStart
1277
1278    def requiresClientCallForStop(self):
1279        """ Returns if this booking belongs to a plugin who requires a client call when the stop button is pressed.
1280            This attribute will be available in Javascript with the "requiresClientCallForStop" attribute
1281        """
1282        return self._requiresClientCallForStop
1283
1284    def canBeDeleted(self):
1285        """ Returns if this booking can be deleted, in the sense that the "Remove" button will be active and able to be pressed.
1286            This attribute will be available in Javascript with the "canBeDeleted" attribute
1287        """
1288
1289        if not hasattr(self, '_canBeDeleted'):
1290            self._canBeDeleted = True
1291        return self._canBeDeleted
1292
1293    def canBeStarted(self):
1294        """ Returns if this booking can be started, in the sense that the "Start" button will be active and able to be pressed.
1295            This attribute will be available in Javascript with the "canBeStarted" attribute
1296        """
1297        return self._canBeStarted
1298
1299    def canBeStopped(self):
1300        """ Returns if this booking can be stopped, in the sense that the "Stop" button will be active and able to be pressed.
1301            This attribute will be available in Javascript with the "canBeStopped" attribute
1302        """
1303        return self._canBeStopped
1304
1305    def isPermittedToStart(self):
1306        """ Returns if this booking is allowed to start, in the sense that it will be started after the "Start" button is pressed.
1307            For example a booking should not be permitted to start before a given time, even if the button is active.
1308            This attribute will be available in Javascript with the "isPermittedToStart" attribute
1309        """
1310        return self._permissionToStart
1311
1312    def isPermittedToStop(self):
1313        """ Returns if this booking is allowed to stop, in the sense that it will be started after the "Stop" button is pressed.
1314            This attribute will be available in Javascript with the "isPermittedToStop" attribute
1315        """
1316        return self._permissionToStop
1317
1318    def needsBookingParamsCheck(self):
1319        """ Returns if this booking belongs to a plugin that needs to verify the booking parameters.
1320        """
1321        return self._needsBookingParamsCheck
1322
1323    def needsToBeNotifiedOnView(self):
1324        """ Returns if this booking needs to be notified when someone views it (for example when the list of bookings is returned)
1325        """
1326        return self._needsToBeNotifiedOnView
1327
1328    def canBeNotifiedOfEventDateChanges(self):
1329        """ Returns if bookings of this type should be able to be notified
1330            of their owner Event changing start date, end date or timezone.
1331        """
1332        return self._canBeNotifiedOfEventDateChanges
1333
1334    def needsToBeNotifiedOfDateChanges(self):
1335        """ Returns if this booking in particular needs to be notified
1336            of their owner Event changing start date, end date or timezone.
1337        """
1338        return self._needsToBeNotifiedOfDateChanges
1339
1340    def setNeedsToBeNotifiedOfDateChanges(self, needsToBeNotifiedOfDateChanges):
1341        """ Sets if this booking in particular needs to be notified
1342            of their owner Event changing start date, end date or timezone.
1343        """
1344        self._needsToBeNotifiedOfDateChanges = needsToBeNotifiedOfDateChanges
1345
1346    def isHidden(self):
1347        """ Return if this booking is "hidden"
1348            A hidden booking will not appear in display pages
1349        """
1350        if not hasattr(self, '_hidden'):
1351            self._hidden = False
1352        return self._hidden
1353
1354    def setHidden(self, hidden):
1355        """ Sets if this booking is "hidden"
1356            A hidden booking will not appear in display pages
1357            hidden: a Boolean
1358        """
1359        self._hidden = hidden
1360
1361    def isAllowMultiple(self):
1362        """ Returns if this booking belongs to a type that allows multiple bookings per event.
1363        """
1364        return self._allowMultiple
1365
1366    def shouldBeIndexed(self):
1367        """ Returns if bookings of this type should be indexed
1368        """
1369        return self._shouldBeIndexed
1370
1371    def getCommonIndexes(self):
1372        """ Returns a list of strings with the names of the
1373            common (shared) indexes that bookings of this type want to
1374            be included in.
1375        """
1376        return self._commonIndexes
1377
1378    def getModificationURL(self):
1379        return urlHandlers.UHConfModifCollaboration.getURL(self.getConference(),
1380                                                           secure = CollaborationTools.isUsingHTTPS(),
1381                                                           tab = CollaborationTools.getPluginTab(self.getPlugin()))
1382
1383    def hasStartDate(self):
1384        """ Returns if bookings of this type have a start date
1385            (they may only have creation / modification date)
1386        """
1387        return self._hasStartDate
1388
1389    def hasTitle(self):
1390        """ Returns if bookings of this type have a title
1391        """
1392        return self._hasTitle
1393
1394    def hasEventDisplay(self):
1395        """ Returns if the type of this booking should display something on
1396            an event display page
1397        """
1398        return self._hasEventDisplay
1399
1400    def isAdminOnly(self):
1401        """ Returns if this booking / this booking's plugin pages should only be displayed
1402            to Server Admins, Video Service Admins, or the respective plugin admins.
1403        """
1404        return self._adminOnly
1405
1406    def _create(self):
1407        """ To be overriden by inheriting classes.
1408            This method is called when a booking is created, after setting the booking parameters.
1409            The plugin should decide if the booking is accepted or not.
1410            Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin,
1411            or a EVO HTTP server in the EVO case.
1412        """
1413        raise CollaborationException("Method _create was not overriden for the plugin type " + str(self._type))
1414
1415    def _modify(self, oldBookingParams):
1416        """ To be overriden by inheriting classes.
1417            This method is called when a booking is modifying, after setting the booking parameters.
1418            The plugin should decide if the booking is accepted or not.
1419            Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin
1420            or a EVO HTTP server in the EVO case.
1421            A dictionary with the previous booking params is passed. This dictionary is the one obtained
1422            by the method self.getBookingParams() before the new params input by the user are applied.
1423        """
1424        raise CollaborationException("Method _modify was not overriden for the plugin type " + str(self._type))
1425
1426    def _start(self):
1427        """ To be overriden by inheriting classes
1428            This method is called when the user presses the "Start" button in a plugin who has a "Start" concept
1429            and whose flag _requiresServerCallForStart is True.
1430            Often this will involve communication with another entity.
1431        """
1432        if self.hasStart():
1433            raise CollaborationException("Method _start was not overriden for the plugin type " + str(self._type))
1434        else:
1435            pass
1436
1437    def _stop(self):
1438        """ To be overriden by inheriting classes
1439            This method is called when the user presses the "Stop" button in a plugin who has a "Stop" concept
1440            and whose flag _requiresServerCallForStop is True.
1441            Often this will involve communication with another entity.
1442        """
1443        if self.hasStop():
1444            raise CollaborationException("Method _stop was not overriden for the plugin type " + str(self._type))
1445        else:
1446            pass
1447
1448    def _checkStatus(self):
1449        """ To be overriden by inheriting classes
1450            This method is called when the user presses the "Check Status" button in a plugin who has a "check status" concept.
1451            Often this will involve communication with another entity.
1452        """
1453        if self.hasCheckStatus():
1454            raise CollaborationException("Method _checkStatus was not overriden for the plugin type " + str(self._type))
1455        else:
1456            pass
1457
1458    def _accept(self):
1459        """ To be overriden by inheriting classes
1460            This method is called when a user with privileges presses the "Accept" button
1461            in a plugin who has a "accept or reject" concept.
1462            Often this will involve communication with another entity.
1463        """
1464        if self.hasAcceptReject():
1465            raise CollaborationException("Method _accept was not overriden for the plugin type " + str(self._type))
1466        else:
1467            pass
1468
1469    def _reject(self):
1470        """ To be overriden by inheriting classes
1471            This method is called when a user with privileges presses the "Reject" button
1472            in a plugin who has a "accept or reject" concept.
1473            Often this will involve communication with another entity.
1474        """
1475        if self.hasAcceptReject():
1476            raise CollaborationException("Method _reject was not overriden for the plugin type " + str(self._type))
1477        else:
1478            pass
1479
1480    def _notifyOnView(self):
1481        """ To be overriden by inheriting classes
1482            This method is called when a user "sees" a booking, for example when the list of bookings is displayed.
1483            Maybe in this moment the booking wants to update its status.
1484        """
1485        if self.needsToBeNotifiedOnView():
1486            raise CollaborationException("Method _notifyOnView was not overriden for the plugin type " + str(self._type))
1487        else:
1488            pass
1489
1490    def _delete(self):
1491        """ To be overriden by inheriting classes
1492            This method is called whent he user removes a booking. Maybe the plugin will need to liberate
1493            ressources that were allocated to it.
1494            This method does not unregister the booking from the list of date change observer of the meeting
1495        """
1496        raise CollaborationException("Method _delete was not overriden for the plugin type " + str(self._type))
1497
1498
1499class WCSTemplateBase(wcomponents.WTemplated):
1500    """ Base class for Collaboration templates.
1501        It stores the following attributes:
1502            _conf : the corresponding Conference object.
1503            _pluginName: the corresponding plugin ("EVO", "DummyPlugin", etc.).
1504            _XXXOptions: a dictionary whose values are the options of the plugin called pluginName.
1505                         So, for example, if an EVO template inherits from this class, an attribute self._EVOOptions will be available.
1506        This class also overloads the _setTPLFile method so that Indico knows where each plugin's *.tpl files are.
1507    """
1508
1509    def __init__(self, pluginName):
1510        """ Constructor for the WCSTemplateBase class.
1511            conf: a Conference object
1512            pluginName: a string with the corresponding plugin name ("EVO", "DummyPlugin", etc.)
1513        """
1514        self._pluginName = pluginName
1515        self._ph = PluginsHolder()
1516
1517        self._plugin = CollaborationTools.getPlugin(pluginName)
1518        setattr(self, "_" + pluginName + "Options", CollaborationTools.getPlugin(pluginName).getOptions())
1519
1520    def _setTPLFile(self):
1521        dir = os.path.join(Collaboration.__path__[0], self._pluginName, "tpls")
1522        file = "%s.tpl"%self.tplId
1523        self.tplFile = os.path.join(dir, file)
1524
1525        hfile = self._getSpecificTPL(os.path.join(dir,self._pluginName,'chelp'), self.tplId,extension='wohl')
1526        self.helpFile = os.path.join(dir,'chelp',hfile)
1527
1528
1529class WCSPageTemplateBase(WCSTemplateBase):
1530    """ Base class for Collaboration templates for the create / modify booking form.
1531    """
1532
1533    def __init__(self, conf, pluginName, user):
1534        WCSTemplateBase.__init__(self, pluginName)
1535        self._conf = conf
1536        self._user = user
1537
1538
1539class WJSBase(WCSTemplateBase):
1540    """ Base class for Collaboration templates for Javascript code template.
1541        It overloads _setTPLFile so that indico can find the Main.js, Extra.js and Indexing.js files.
1542    """
1543    def __init__(self, conf, pluginName, user):
1544        WCSTemplateBase.__init__(self, pluginName)
1545        self._conf = conf
1546        self._user = user
1547
1548    def _setTPLFile(self):
1549        dir = os.path.join(Collaboration.__path__[0], self._pluginName, "tpls")
1550        file = "%s.js"%self.tplId
1551        self.tplFile = os.path.join(dir, file)
1552        self.helpFile = ''
1553
1554class WCSCSSBase(WCSTemplateBase):
1555    """ Base class for Collaboration templates for CSS code template
1556        It overloads _setTPLFile so that indico can find the style.css files.
1557    """
1558
1559    def _setTPLFile(self):
1560        dir = os.path.join(Collaboration.__path__[0], self._pluginName)
1561        file = "%s.css"%self.tplId
1562        self.tplFile = os.path.join(dir, file)
1563        self.helpFile = ''
1564
1565class CSErrorBase(Fossilizable):
1566    fossilizes(ICSErrorBaseFossil)
1567
1568    """ When _create, _modify or _remove want to return an error,
1569        they should return an error that inherits from this class
1570    """
1571
1572    def __init__(self):
1573        pass
1574
1575    def getUserMessage(self):
1576        """ To be overloaded.
1577            Returns the string that will be shown to the user when this error will happen.
1578        """
1579        raise CollaborationException("Method getUserMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
1580
1581    def getLogMessage(self):
1582        """ To be overloaded.
1583            Returns the string that will be printed in Indico's log when this error will happen.
1584        """
1585        raise CollaborationException("Method getLogMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
1586
1587class CSSanitizationError(CSErrorBase): #already Fossilizable
1588    fossilizes(ICSSanitizationErrorFossil)
1589
1590    """ Class used to return which fields have a sanitization error (invalid html / script tags)
1591    """
1592
1593    def __init__(self, invalidFields):
1594        self._invalidFields = invalidFields
1595
1596    def invalidFields(self):
1597        return self._invalidFields
1598
1599
1600class CollaborationException(MaKaCError):
1601    """ Error for the Collaboration System "core". Each plugin should declare their own EVOError, etc.
1602    """
1603    def __init__(self, msg, area = 'Collaboration', inner = None):
1604        MaKaCError.__init__(self, msg, area)
1605        self._inner = inner
1606
1607    def getInner(self):
1608        return self._inner
1609
1610    def __str__(self):
1611        return MaKaCError.__str__(self) + '. Inner: ' + str(self._inner)
1612
1613class CollaborationServiceException(ServiceError):
1614    """ Error for the Collaboration System "core", for Service calls.
1615    """
1616    def __init__(self, message, inner = None):
1617        ServiceError.__init__(self, "ERR-COLL", message, inner)
Note: See TracBrowser for help on using the repository browser.