[FIX] Method naming
[cds-indico.git] / indico / MaKaC / plugins / Collaboration / base.py
blob704ad83995150f7fa6d79652b2319c08be46e74a
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.
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.
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 from indico.util.contextManager import ContextManager
21 import time
22 from persistent import Persistent
23 from hashlib import md5
24 from MaKaC.common.Counter import Counter
25 from MaKaC.common.utils import formatDateTime, parseDateTime
26 from MaKaC.common.timezoneUtils import getAdjustedDate, setAdjustedDate,\
27 datetimeToUnixTimeInt
28 from MaKaC.webinterface import wcomponents, urlHandlers
29 from MaKaC.plugins import PluginsHolder
30 from MaKaC.errors import MaKaCError, NoReportError
31 from MaKaC.services.interface.rpc.common import ServiceError
32 from MaKaC.common.timezoneUtils import nowutc
33 from MaKaC.common.logger import Logger
34 from MaKaC.common.indexes import IndexesHolder
35 from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools,\
36 MailTools
37 from MaKaC.conference import Observer
38 from MaKaC.webinterface.common.tools import hasTags
39 from MaKaC.plugins.Collaboration import mail
40 from MaKaC.common.mail import GenericMailer
41 import os, inspect
42 import MaKaC.plugins.Collaboration as Collaboration
43 from indico.modules.scheduler.client import Client
44 from indico.modules.scheduler.tasks import HTTPTask
45 from indico.util import json
46 from indico.util.i18n import gettext_lazy
47 from indico.util.date_time import now_utc
48 from MaKaC.common.fossilize import Fossilizable, fossilizes
49 from MaKaC.common.externalOperationsManager import ExternalOperationsManager
50 from BTrees.OOBTree import OOBTree
52 from MaKaC.plugins.Collaboration.fossils import ICSErrorBaseFossil, ICSSanitizationErrorFossil,\
53 ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil,\
54 ISpeakerWrapperBaseFossil
55 from MaKaC.conference import Contribution
58 class CSBookingManager(Persistent, Observer):
59 """ Class for managing the bookins of a meeting.
60 It will store the list of bookings. Adding / removing / editing bookings should be through this class.
61 """
63 _shouldBeTitleNotified = True
64 _shouldBeDateChangeNotified = True
65 _shouldBeLocationChangeNotified = True
66 _shouldBeDeletionNotified = True
68 def __init__(self, conf):
69 """ Constructor for the CSBookingManager class.
70 conf: a Conference object. The meeting that owns this CSBookingManager.
71 """
72 self._conf = conf
73 self._counter = Counter(1)
74 # a dict where the bookings will be stored. The key will be the booking id, the value a CSBookingBase object.
75 self._bookings = {}
77 # an index of bookings by type. The key will be a booking type (string), the value a list of booking id
78 self._bookingsByType = {}
80 # an index of bookings to video services by event.uniqueId : video.uniqueId pairind.
81 self._bookingsToVideoServices = OOBTree()
83 # a list of ids with hidden bookings
84 self._hiddenBookings = set()
86 # an index of video services managers for each plugin. key: plugin name, value: list of users
87 self._managers = {}
89 # list of speaker wrapper for a conference
90 self._speakerWrapperList = []
91 self.updateSpeakerWrapperList()
93 def getOwner(self):
94 """ Returns the Conference (the meeting) that owns this CSBookingManager object.
95 """
96 return self._conf
98 def isCSAllowed(self, user = None):
99 """ Returns if the associated event should display a Video Services tab
100 This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
101 If a user is provided, we will take into account if the user can manage the plugin (for example,
102 an event manager cannot manage an admin-only plugin)
104 pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
105 if pluginsPerEventType:
106 for plugin in pluginsPerEventType[self._conf.getType()]:
107 if plugin.isActive() and (user is None or CollaborationTools.canUserManagePlugin(self._conf, plugin, user)):
108 return True
109 return False
111 def getAllowedPlugins(self):
112 """ Returns a list of allowed plugins (Plugin objects) for this event.
113 Only active plugins are returned.
114 This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
116 pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
117 if pluginsPerEventType is not None:
118 allowedForThisEvent = pluginsPerEventType[self._conf.getType()]
119 return [plugin for plugin in allowedForThisEvent if plugin.isActive()]
122 def getBookingList(self, sorted = False, filterByType = None, notify = False, onlyPublic = False):
123 """ Returns a list of all the bookings.
124 If sorted = True, the list of bookings will be sorted by id.
125 If filterByType = None, all bookings are returned.
126 Otherwise, just those of the type "filterByType" if filterByType is a string,
127 or if it is a list of strings, those who have a type included in filterByType.
130 if not hasattr(self, "_bookingsByType"): #TODO: remove when safe
131 self._bookingsByType = {}
133 if filterByType is not None:
134 if type(filterByType) == str:
135 keys = self._bookingsByType.get(filterByType, [])
136 if type(filterByType) == list:
137 keys = []
138 for pluginName in filterByType:
139 keys.extend(self._bookingsByType.get(pluginName, []))
140 else:
141 keys = self._bookings.keys()
143 if onlyPublic and self.getHiddenBookings():
144 keys = set(keys)
145 keys = keys.difference(self.getHiddenBookings())
146 keys = list(keys)
148 if sorted:
149 keys.sort(key = lambda k: int(k))
151 bookingList = [self._bookings[k] for k in keys if not self._bookings[k].hasSessionOrContributionLink() or self._bookings[k].getLinkObject()]
153 #we notify all the bookings that they have been viewed. If a booking doesn't need to be viewed, nothing will happen
154 if notify:
155 for booking in bookingList:
156 if booking.needsToBeNotifiedOnView():
157 try:
158 booking._notifyOnView()
159 except Exception, e:
160 Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
162 return bookingList
164 def getBooking(self, id):
165 """ Returns a booking given its id.
167 return self._bookings.get(id,None)
169 def getSingleBooking(self, type, notify = False):
170 """ Returns the single booking of a plugin who only allows one booking.
171 type: a string with the name of the plugin
172 If the plugin actually allows multiple bookings, an exception will be thrown
173 If the plugin has no booking, None will be returned.
174 Otherwise the booking will be returned
176 if CollaborationTools.getCSBookingClass(type)._allowMultiple:
177 raise CollaborationException("Plugin type " + str(type) + " is not a single-booking plugin")
178 blist = self._bookingsByType.get(type,[])
179 if blist:
180 booking = self._bookings[blist[0]]
181 if notify:
182 try:
183 booking._notifyOnView()
184 except Exception, e:
185 Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
186 return booking
187 else:
188 return None
190 def getHiddenBookings(self):
191 if not hasattr(self, '_hiddenBookings'):
192 self._hiddenBookings = set()
193 return self._hiddenBookings
195 def hasBookings(self):
196 return len(self._bookings) > 0
198 def canCreateBooking(self, type):
199 """ Returns if it's possible to create a booking of this given type
201 if not CollaborationTools.getCSBookingClass(type)._allowMultiple:
202 return len(self.getBookingList(filterByType = type)) == 0
203 return True
205 def checkVideoLink(self, bookingParams):
207 if bookingParams.get('videoLinkType',"") == "session":
208 sessSlotId = bookingParams.get("videoLinkSession","")
209 import re
210 regExp = re.match(r"""(s[0-9a]*)(l[0-9]*)""", sessSlotId)
211 if not regExp:
212 raise CollaborationException(_('No session has been passed when the type is session.'))
213 sessionId = regExp.group(1)[1:]
214 slotId = regExp.group(2)[1:]
215 session = self._conf.getSessionById(sessionId)
216 if session is None:
217 raise CollaborationException(_('The session does not exist.'))
218 slot = session.getSlotById(slotId)
219 if slot is None:
220 raise CollaborationException(_('The session does not exist.'))
221 return slot.getUniqueId()
223 elif bookingParams.get('videoLinkType',"") == "contribution":
224 contId = bookingParams.get("videoLinkContribution","")
225 if contId == "":
226 raise CollaborationException(_('No contribution has been passed when the type is contribution.'))
227 cont = self._conf.getContributionById(contId)
228 if cont is None:
229 raise CollaborationException(_('The contribution does not exist.'))
230 return cont.getUniqueId()
232 return self._conf.getUniqueId()
234 def addBooking(self, booking):
235 """ Adds an existing booking to the list of bookings.
237 booking: The existing booking to be added.
239 booking.setId( self._getNewBookingId())
240 self._bookings[booking.getId()] = booking
241 self._bookingsByType.setdefault(booking.getType(),[]).append(booking.getId())
242 if booking.isHidden():
243 self.getHiddenBookings().add(booking.getId())
244 self._indexBooking(booking)
246 booking.index_instances()
248 self._notifyModification()
250 # the unique id can be diferent for the new conference
251 booking.setLinkType({booking.getLinkType():ContextManager.get('clone.unique_id_map').get(booking.getLinkId(),"")})
252 if booking.hasSessionOrContributionLink():
253 linkObject = booking.getLinkObject()
254 bp=booking.getBookingParams()
255 if isinstance(linkObject, Contribution):
256 bp["videoLinkContribution"] = linkObject.getId()
257 else: #session
258 bp["videoLinkSession"] = linkObject.getId()
259 booking.setBookingParams(bp)
261 self.addVideoService(booking.getLinkId(), booking)
263 def createBooking(self, bookingType, bookingParams = {}):
264 """ Adds a new booking to the list of bookings.
265 The id of the new booking is auto-generated incrementally.
266 After generating the booking, its "performBooking" method will be called.
268 bookingType: a String with the booking's plugin. Example: "DummyPlugin", "EVO"
269 bookingParams: a dictionary with the parameters necessary to create the booking.
270 "create the booking" usually means Indico deciding if the booking can take place.
271 if "startDate" and "endDate" are among the keys, they will be taken out of the dictionary.
273 if self.canCreateBooking(bookingType):
275 uniqueId = self.checkVideoLink(bookingParams)
277 if (self.hasVideoService(uniqueId) and bookingParams.has_key("videoLinkType") and bookingParams.get("videoLinkType","") != "event"): # Restriction: 1 video service per session or contribution.
278 raise NoReportError(_('Only one video service per contribution or session is allowed.'))
280 newBooking = CollaborationTools.getCSBookingClass(bookingType)(bookingType, self._conf)
281 if bookingParams.has_key("videoLinkType"):
282 newBooking.setLinkType({bookingParams["videoLinkType"] : uniqueId})
284 error = newBooking.setBookingParams(bookingParams)
286 if isinstance(error, CSErrorBase):
287 return error
288 elif error:
289 raise CollaborationServiceException("Problem while creating a booking of type " + bookingType)
290 else:
291 newId = self._getNewBookingId()
292 newBooking.setId(newId)
293 createResult = newBooking._create()
294 if isinstance(createResult, CSErrorBase):
295 return createResult
296 else:
297 self._bookings[newId] = newBooking
298 self._bookingsByType.setdefault(bookingType,[]).append(newId)
299 if newBooking.isHidden():
300 self.getHiddenBookings().add(newId)
302 newBooking.index_instances()
304 self._indexBooking(newBooking)
305 self._notifyModification()
307 if uniqueId is not None: # if we're here and uniqueId has a value, register the video service.
308 self.addVideoService(uniqueId, newBooking)
310 if MailTools.needToSendEmails(bookingType):
311 newBooking._sendNotifications('new')
313 return newBooking
314 else:
315 #we raise an exception because the web interface should take care of this never actually happening
316 raise CollaborationServiceException(bookingType + " only allows to create 1 booking per event")
318 def _indexBooking(self, booking, index_names=None):
319 indexes = self._getIndexList(booking)
320 if index_names is not None:
321 ci = IndexesHolder().getById('collaboration')
322 all_indexes = list(ci.getIndex(index) for index in index_names)
323 indexes = list(index for index in all_indexes if index in indexes)
325 if booking.shouldBeIndexed():
326 for index in indexes:
327 index.indexBooking(booking)
329 def changeBooking(self, bookingId, bookingParams):
331 Changes the bookingParams of a CSBookingBase object.
332 After updating the booking, its 'performBooking' method will be called.
333 bookingId: the id of the CSBookingBase object to change
334 bookingParams: a dictionary with the new parameters that will modify the booking
335 'modify the booking' can mean that maybe the booking will be rejected with the new parameters.
336 if 'startDate' and 'endDate' are among the keys, they will be taken out of the dictionary.
338 booking = self.getBooking(bookingId)
340 oldStartDate = booking.getStartDate()
341 oldModificationDate = booking.getModificationDate()
342 oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
344 booking.unindex_instances()
346 error = booking.setBookingParams(bookingParams)
347 if isinstance(error, CSSanitizationError):
348 return error
349 elif error:
350 CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
351 if isinstance(error, CSErrorBase):
352 return error
353 raise CollaborationServiceException("Problem while modifying a booking of type " + booking.getType())
354 else:
355 modifyResult = booking._modify(oldBookingParams)
356 if isinstance(modifyResult, CSErrorBase):
357 CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
358 return modifyResult
359 else:
360 modificationDate = now_utc()
361 booking.setModificationDate(modificationDate)
363 if booking.isHidden():
364 self.getHiddenBookings().add(booking.getId())
365 elif booking.getId() in self.getHiddenBookings():
366 self.getHiddenBookings().remove(booking.getId())
368 eventLinkUpdated = False
369 newLinkId = self.checkVideoLink(bookingParams)
371 if booking.hasSessionOrContributionLink():
372 oldLinkData = booking.getLinkIdDict()
373 oldLinkId = oldLinkData.values()[0]
375 # Details changed, we need to remove the association and re-create it
376 if not (oldLinkData.has_key(bookingParams.get('videoLinkType','')) and oldLinkId == newLinkId):
377 self.removeVideoSingleService(booking.getLinkId(), booking)
378 eventLinkUpdated = True
380 if eventLinkUpdated or (bookingParams.has_key("videoLinkType") and bookingParams.get("videoLinkType","") != "event"):
381 if self.hasVideoService(booking.getLinkId(), booking):
382 pass # No change in the event linking
383 elif newLinkId is not None:
384 if (self.hasVideoService(newLinkId) and bookingParams.has_key("videoLinkType") and bookingParams.get("videoLinkType","") != "event"): # Restriction: 1 video service per session or contribution.
385 raise NoReportError(_('Only one video service per contribution or session is allowed.'))
386 else:
387 self.addVideoService(newLinkId, booking)
388 if bookingParams.has_key("videoLinkType"):
389 booking.setLinkType({bookingParams['videoLinkType']: newLinkId})
390 else: # If it's still None, event linking has been completely removed.
391 booking.resetLinkParams()
393 self._changeStartDateInIndex(booking, oldStartDate, booking.getStartDate())
394 self._changeModificationDateInIndex(booking, oldModificationDate, modificationDate)
395 booking.index_instances()
397 if booking.hasAcceptReject():
398 if booking.getAcceptRejectStatus() is not None:
399 booking.clearAcceptRejectStatus()
400 self._addToPendingIndex(booking)
402 self._notifyModification()
404 if MailTools.needToSendEmails(booking.getType()):
405 booking._sendNotifications('modify')
407 return booking
409 @classmethod
410 def _rollbackChanges(cls, booking, oldBookingParams, oldModificationDate):
411 booking.setBookingParams(oldBookingParams)
412 booking.setModificationDate(oldModificationDate)
414 def _changeConfTitleInIndex(self, booking, oldTitle, newTitle):
415 if booking.shouldBeIndexed():
416 indexes = self._getIndexList(booking)
417 for index in indexes:
418 index.changeEventTitle(booking, oldTitle, newTitle)
420 def _changeStartDateInIndex(self, booking, oldStartDate, newStartDate):
421 if booking.shouldBeIndexed() and booking.hasStartDate():
422 indexes = self._getIndexList(booking)
423 for index in indexes:
424 index.changeStartDate(booking, oldStartDate, newStartDate)
426 def _changeModificationDateInIndex(self, booking, oldModificationDate, newModificationDate):
427 if booking.shouldBeIndexed():
428 indexes = self._getIndexList(booking)
429 for index in indexes:
430 index.changeModificationDate(booking, oldModificationDate, newModificationDate)
432 def _changeConfStartDateInIndex(self, booking, oldConfStartDate, newConfStartDate):
433 if booking.shouldBeIndexed():
434 indexes = self._getIndexList(booking)
435 for index in indexes:
436 index.changeConfStartDate(booking, oldConfStartDate, newConfStartDate)
438 def removeBooking(self, id):
439 """ Removes a booking given its id.
441 booking = self.getBooking(id)
442 bookingType = booking.getType()
443 bookingLinkId = booking.getLinkId()
445 removeResult = booking._delete()
446 if isinstance(removeResult, CSErrorBase):
447 return removeResult
448 else:
449 del self._bookings[id]
450 self._bookingsByType[bookingType].remove(id)
451 if not self._bookingsByType[bookingType]:
452 del self._bookingsByType[bookingType]
453 if id in self.getHiddenBookings():
454 self.getHiddenBookings().remove(id)
456 # If there is an association to a session or contribution, remove it
457 if bookingLinkId is not None:
458 self.removeVideoSingleService(bookingLinkId, booking)
460 booking.unindex_instances()
462 self._unindexBooking(booking)
464 self._notifyModification()
466 if MailTools.needToSendEmails(booking.getType()):
467 booking._sendNotifications('remove')
469 return booking
471 def _unindexBooking(self, booking):
472 if booking.shouldBeIndexed():
473 indexes = self._getIndexList(booking)
474 for index in indexes:
475 index.unindexBooking(booking)
477 def startBooking(self, id):
478 booking = self._bookings[id]
479 if booking.canBeStarted():
480 booking._start()
481 return booking
482 else:
483 raise CollaborationException(_("Tried to start booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be started."))
485 def stopBooking(self, id):
486 booking = self._bookings[id]
487 if booking.canBeStopped():
488 booking._stop()
489 return booking
490 else:
491 raise CollaborationException(_("Tried to stop booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be stopped."))
493 def connectBooking(self, id):
494 booking = self._bookings[id]
495 if booking.canBeConnected():
496 return booking._connect()
497 else:
498 raise CollaborationException(_("Tried to connect booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be connected."))
500 def checkBookingStatus(self, id):
501 booking = self._bookings[id]
502 if booking.hasCheckStatus():
503 result = booking._checkStatus()
504 if isinstance(result, CSErrorBase):
505 return result
506 else:
507 return booking
508 else:
509 raise ServiceError(message=_("Tried to check status of booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking does not support the check status service."))
511 def acceptBooking(self, id, user = None):
512 booking = self._bookings[id]
513 if booking.hasAcceptReject():
514 if booking.getAcceptRejectStatus() is None:
515 self._removeFromPendingIndex(booking)
516 booking.accept(user)
517 return booking
518 else:
519 raise ServiceError(message=_("Tried to accept booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be accepted."))
521 def rejectBooking(self, id, reason):
522 booking = self._bookings[id]
523 if booking.hasAcceptReject():
524 if booking.getAcceptRejectStatus() is None:
525 self._removeFromPendingIndex(booking)
526 booking.reject(reason)
527 return booking
528 else:
529 raise ServiceError("ERR-COLL10", _("Tried to reject booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be rejected."))
531 def makeMeModeratorBooking(self, id, user):
532 booking = self._bookings[id]
533 bookingParams = booking.getBookingParams()
534 bookingParams["owner"] = user
535 return self.changeBooking(id,bookingParams)
537 def _addToPendingIndex(self, booking):
538 if booking.shouldBeIndexed():
539 indexes = self._getPendingIndexList(booking)
540 for index in indexes:
541 index.indexBooking(booking)
543 def _removeFromPendingIndex(self, booking):
544 if booking.shouldBeIndexed():
545 indexes = self._getPendingIndexList(booking)
546 for index in indexes:
547 index.unindexBooking(booking)
549 def _getNewBookingId(self):
550 return self._counter.newCount()
552 def _getIndexList(self, booking):
553 """ Returns a list of BookingsIndex objects where the booking should be indexed.
554 This list includes:
555 -an index of all bookings
556 -an index of bookings of the given type
557 -an index of all bookings in the category of the event
558 -an index of booking of the given type, in the category of the event
559 If the booking type declared common indexes:
560 -the common indexes
561 -the common indexes for the category of the event
562 If the booking is of the Accept/Reject type
563 -same indexes as above, but only for pending bookings
565 collaborationIndex = IndexesHolder().getById("collaboration")
566 indexes = [collaborationIndex.getAllBookingsIndex(),
567 collaborationIndex.getIndex(booking.getType())]
569 for commonIndexName in booking.getCommonIndexes():
570 indexes.append(collaborationIndex.getIndex(commonIndexName))
572 if booking.hasAcceptReject() and booking.getAcceptRejectStatus() is None:
573 indexes.extend(self._getPendingIndexList(booking))
575 return indexes
577 def _getPendingIndexList(self, booking):
578 collaborationIndex = IndexesHolder().getById("collaboration")
579 indexes = [collaborationIndex.getIndex("all_pending"),
580 collaborationIndex.getIndex(booking.getType() + "_pending")]
582 for commonIndexName in booking.getCommonIndexes():
583 indexes.append(collaborationIndex.getIndex(commonIndexName + "_pending"))
585 return indexes
587 def getManagers(self):
588 if not hasattr(self, "_managers"):
589 self._managers = {}
590 return self._managers
592 def addPluginManager(self, plugin, user):
593 #TODO: use .linkTo on the user. To be done when the list of roles of a user is actually needed for smth...
594 self.getManagers().setdefault(plugin, []).append(user)
595 self._notifyModification()
597 def removePluginManager(self, plugin, user):
598 #TODO: use .unlinkTo on the user. To be done when the list of roles of a user is actually needed for smth...
599 if user in self.getManagers().setdefault(plugin,[]):
600 self.getManagers()[plugin].remove(user)
601 self._notifyModification()
603 def getVideoServicesManagers(self):
604 return self.getManagers().setdefault('all', [])
606 def isVideoServicesManager(self, user):
607 return user in self.getManagers().setdefault('all', [])
609 def getPluginManagers(self, plugin):
610 return self.getManagers().setdefault(plugin, [])
612 def isPluginManager(self, plugin, user):
613 return user in self.getManagers().setdefault(plugin, [])
615 def getAllManagers(self):
616 """ Returns a list with all the managers, no matter their type
617 The returned list is not ordered.
619 managers = set()
620 for managerList in self.getManagers().itervalues():
621 managers = managers.union(managerList)
622 return list(managers)
624 def isPluginManagerOfAnyPlugin(self, user):
625 #TODO: this method is not optimal. to be optimal, we should store somewhere an index where the key
626 #is the user, and the value is a list of plugins where they are managers.
627 #this could be done with .getLinkTo, but we would need to change the .linkTo method to add extra information
628 #(since we cannot create a role for each plugin)
629 if self.isVideoServicesManager(user):
630 return True
631 else:
632 for plugin in self.getManagers().iterkeys():
633 if self.isPluginManager(plugin, user):
634 return True
635 return False
637 def notifyTitleChange(self, oldTitle, newTitle):
638 """ Notifies the CSBookingManager that the title of the event (meeting) it's attached to has changed.
639 The CSBookingManager will reindex all its bookings in the event title index.
640 This method will be called by the event (meeting) object
642 for booking in self.getBookingList():
643 try:
644 self._changeConfTitleInIndex(booking, oldTitle, newTitle)
645 except Exception, e:
646 Logger.get('VideoServ').exception("Exception while reindexing a booking in the event title index because its event's title changed: " + str(e))
648 def notifyInfoChange(self):
649 self.updateSpeakerWrapperList()
651 def notifyEventDateChanges(self, oldStartDate = None, newStartDate = None, oldEndDate = None, newEndDate = None):
652 """ Notifies the CSBookingManager that the start and / or end dates of the event it's attached to have changed.
653 The CSBookingManager will change the dates of all the bookings that want to be updated.
654 If there are problems (such as a booking not being able to be modified)
655 it will write a list of strings describing the problems in the 'dateChangeNotificationProblems' context variable.
656 (each string is produced by the _booking2NotifyProblem method).
657 This method will be called by the event (meeting) object.
659 startDateChanged = oldStartDate is not None and newStartDate is not None and not oldStartDate == newStartDate
660 endDateChanged = oldEndDate is not None and newEndDate is not None and not oldEndDate == newEndDate
661 someDateChanged = startDateChanged or endDateChanged
663 Logger.get("VideoServ").info("""CSBookingManager: starting notifyEventDateChanges. Arguments: confId=%s, oldStartDate=%s, newStartDate=%s, oldEndDate=%s, newEndDate=%s""" %
664 (str(self._conf.getId()), str(oldStartDate), str(newStartDate), str(oldEndDate), str(newEndDate)))
666 if someDateChanged:
667 problems = []
668 for booking in self.getBookingList():
670 # booking "instances" provide higher granularity in search
671 booking.unindex_instances()
672 booking.index_instances()
674 if booking.hasStartDate():
675 if startDateChanged:
676 try:
677 self._changeConfStartDateInIndex(booking, oldStartDate, newStartDate)
678 except Exception, e:
679 Logger.get('VideoServ').error("Exception while reindexing a booking in the event start date index because its event's start date changed: " + str(e))
681 if booking.needsToBeNotifiedOfDateChanges():
682 Logger.get("VideoServ").info("""CSBookingManager: notifying date changes to booking %s of event %s""" %
683 (str(booking.getId()), str(self._conf.getId())))
684 oldBookingStartDate = booking.getStartDate()
685 oldBookingEndDate = booking.getEndDate()
686 oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
688 if startDateChanged:
689 booking.setStartDate(oldBookingStartDate + (newStartDate - oldStartDate) )
690 if endDateChanged:
691 booking.setEndDate(oldBookingEndDate + (newEndDate - oldEndDate) )
693 rollback = False
694 modifyResult = None
695 try:
696 modifyResult = booking._modify(oldBookingParams)
697 if isinstance(modifyResult, CSErrorBase):
698 Logger.get('VideoServ').warning("""Error while changing the dates of booking %s of event %s after event dates changed: %s""" %
699 (str(booking.getId()), str(self._conf.getId()), modifyResult.getLogMessage()))
700 rollback = True
701 except Exception, e:
702 Logger.get('VideoServ').error("""Exception while changing the dates of booking %s of event %s after event dates changed: %s""" %
703 (str(booking.getId()), str(self._conf.getId()), str(e)))
704 rollback = True
706 if rollback:
707 booking.setStartDate(oldBookingStartDate)
708 booking.setEndDate(oldBookingEndDate)
709 problems.append(CSBookingManager._booking2NotifyProblem(booking, modifyResult))
710 elif startDateChanged:
711 self._changeStartDateInIndex(booking, oldBookingStartDate, booking.getStartDate())
713 if hasattr(booking, "notifyEventDateChanges"):
714 try:
715 booking.notifyEventDateChanges(oldStartDate, newStartDate, oldEndDate, newEndDate)
716 except Exception, e:
717 Logger.get('VideoServ').exception("Exception while notifying a plugin of an event date changed: " + str(e))
719 if problems:
720 ContextManager.get('dateChangeNotificationProblems')['Collaboration'] = [
721 'Some Video Services bookings could not be moved:',
722 problems,
723 'Go to [[' + str(urlHandlers.UHConfModifCollaboration.getURL(self.getOwner(), secure = ContextManager.get('currentRH').use_https())) + ' the Video Services section]] to modify them yourself.'
727 def notifyTimezoneChange(self, oldTimezone, newTimezone):
728 """ Notifies the CSBookingManager that the timezone of the event it's attached to has changed.
729 The CSBookingManager will change the dates of all the bookings that want to be updated.
730 This method will be called by the event (Conference) object
732 return []
734 def notifyLocationChange(self):
735 for booking in self.getBookingList():
736 if hasattr(booking, "notifyLocationChange"):
737 try:
738 booking.notifyLocationChange()
739 except Exception, e:
740 Logger.get('VideoServ').exception("Exception while notifying a plugin of a location change: " + str(e))
742 @classmethod
743 def _booking2NotifyProblem(cls, booking, modifyError):
744 """ Turns a booking into a string used to tell the user
745 why a date change of a booking triggered by the event's start or end date change
746 went bad.
749 message = []
750 message.extend(["The dates of the ", booking.getType(), " booking"])
751 if booking.hasTitle():
752 message.extend([': "', booking._getTitle(), '" (', booking.getStartDateAsString(), ' - ', booking.getEndDateAsString(), ')'])
753 else:
754 message.extend([' ongoing from ', booking.getStartDateAsString(), ' to ', booking.getEndDateAsString(), ''])
756 message.append(' could not be changed.')
757 if modifyError and modifyError.getUserMessage():
758 message.extend([' Reason: ', modifyError.getUserMessage()])
759 return "".join(message)
762 def notifyDeletion(self):
763 """ Notifies the CSBookingManager that the Conference object it is attached to has been deleted.
764 The CSBookingManager will change the dates of all the bookings that want to be updated.
765 This method will be called by the event (Conference) object
767 for booking in self.getBookingList():
768 try:
769 if booking.getType() != "Vidyo":
770 removeResult = booking._delete()
771 if isinstance(removeResult, CSErrorBase):
772 Logger.get('VideoServ').warning("Error while deleting a booking of type %s after deleting an event: %s"%(booking.getType(), removeResult.getLogMessage() ))
773 booking.unindex_instances()
774 self._unindexBooking(booking)
775 except Exception, e:
776 Logger.get('VideoServ').exception("Exception while deleting a booking of type %s after deleting an event: %s" % (booking.getType(), str(e)))
778 def getEventDisplayPlugins(self, sorted = False):
779 """ Returns a list of names (strings) of plugins which have been configured
780 as showing bookings in the event display page, and which have bookings
781 already (or previously) created in the event.
782 (does not check if the bookings are hidden or not)
785 pluginsWithEventDisplay = CollaborationTools.pluginsWithEventDisplay()
786 l = []
787 for pluginName in self._bookingsByType:
788 if pluginName in pluginsWithEventDisplay:
789 l.append(pluginName)
790 if sorted:
791 l.sort()
792 return l
794 def createTestBooking(self, bookingParams = {}):
795 """ Function that creates a 'test' booking for performance test.
796 Avoids to use any of the plugins except DummyPlugin
798 from MaKaC.plugins.Collaboration.DummyPlugin.collaboration import CSBooking as DummyBooking
799 bookingType = 'DummyPlugin'
800 newBooking = DummyBooking(bookingType, self._conf)
801 error = newBooking.setBookingParams(bookingParams)
802 if error:
803 raise CollaborationServiceException("Problem while creating a test booking")
804 else:
805 newId = self._getNewBookingId()
806 newBooking.setId(newId)
807 createResult = newBooking._create()
808 if isinstance(createResult, CSErrorBase):
809 return createResult
810 else:
811 self._bookings[newId] = newBooking
812 self._bookingsByType.setdefault(bookingType,[]).append(newId)
813 if newBooking.isHidden():
814 self.getHiddenBookings().add(newId)
815 self._indexBooking(newBooking)
816 self._notifyModification()
817 return newBooking
819 def _notifyModification(self):
820 self._p_changed = 1
822 def getSortedContributionSpeaker(self, exclusive):
823 ''' This method will create a dictionary by sorting the contribution/speakers
824 that they are in recording, webcast or in both.
825 bool: exclusive - if True, every dicts (recording, webcast, both) will
826 have different speaker list (no repetition allowed)
827 if an element is present in 'both', it will be deleted from
828 'recording and 'webcast'
830 returns d = { 'recording': {}, 'webcast' : {}, 'both': {} }
833 d = {}
835 recordingBooking = self.getSingleBooking("RecordingRequest")
836 webcastBooking = self.getSingleBooking("WebcastRequest")
838 d["recording"] = recordingBooking.getContributionSpeakerSingleBooking() if recordingBooking else {}
839 d["webcast"] = webcastBooking.getContributionSpeakerSingleBooking() if webcastBooking else {}
841 contributions = {}
842 ''' Look for speaker intersections between 'recording' and 'webcast' dicts
843 and put them in 'both' dict. Additionally, if any intersection has been found,
844 we exclude them from the original dictionary.
846 for cont in d["recording"].copy():
847 if cont in d["webcast"].copy():
848 # Check if same contribution/speaker in 'recording' and 'webcast'
849 intersection = set(d['recording'][cont]) & set(d['webcast'][cont])
850 if intersection:
851 contributions[cont] = list(intersection)
853 # if exclusive is True, and as we found same contribution/speaker,
854 # we delete them from 'recording' and 'webcast' dicts
855 if exclusive:
856 exclusion = set(d['recording'][cont]) ^ set(contributions[cont])
857 if not exclusion:
858 del d["recording"][cont]
859 else:
860 d["recording"][cont] = list(exclusion)
862 exclusion = set(d['webcast'][cont]) ^ set(contributions[cont])
863 if not exclusion:
864 del d["webcast"][cont]
865 else:
866 d["webcast"][cont] = list(exclusion)
868 d["both"] = contributions
870 return d
872 def getContributionSpeakerByType(self, requestType):
873 ''' Return a plain dict of contribution/speaker according to the requestType
874 if the request type is 'both', we need to merge the lists
876 d = self.getSortedContributionSpeaker(False) # We want non exclusive dict
878 if requestType == "recording":
879 return d['recording']
880 elif requestType == "webcast":
881 return d['webcast']
882 elif requestType == "both":
883 # We merge 'recording' and 'webcast'
884 m = dict(((cont, list(set(spks) | \
885 set(d['webcast'].get(cont, [])))) for cont, spks in d['recording'].iteritems()))
886 m.update(dict((cont, spks) for cont, spks in d['webcast'].iteritems() if cont not in m))
888 return m
889 else:
890 return {}
892 def updateSpeakerWrapperList(self, newList = False):
894 if newList arg is True, don't check if there is an existing speakerWrapperList
895 and create a new one straight forward. (Done to avoid loops)
897 SWList = []
898 contributions = self.getSortedContributionSpeaker(True)
899 requestType = ['recording', 'webcast', 'both']
901 for type in requestType:
902 for cont in contributions[type]:
903 for spk in contributions[type][cont]:
904 if newList:
905 sw = None
906 else:
907 sw = self.getSpeakerWrapperByUniqueId("%s.%s"%(cont, spk.getId()))
909 if sw:
910 if not sw.getObject().getEmail():
911 if sw.getStatus() not in [SpeakerStatusEnum.SIGNED,
912 SpeakerStatusEnum.FROMFILE,
913 SpeakerStatusEnum.REFUSED]:
914 sw.setStatus(SpeakerStatusEnum.NOEMAIL)
915 elif sw.getStatus() == SpeakerStatusEnum.NOEMAIL:
916 sw.setStatus(SpeakerStatusEnum.NOTSIGNED)
917 sw.setRequestType(type)
918 SWList.append(sw)
919 else:
920 newSw = SpeakerWrapper(spk, cont, type)
921 if not newSw.getObject().getEmail():
922 newSw.setStatus(SpeakerStatusEnum.NOEMAIL)
923 SWList.append(newSw)
925 self._speakerWrapperList = SWList
927 def getSpeakerWrapperList(self):
928 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
929 self.updateSpeakerWrapperList(True)
931 return self._speakerWrapperList
933 def getSpeakerWrapperByUniqueId(self, id):
935 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
936 self.updateSpeakerWrapperList(True)
938 for spkWrap in self._speakerWrapperList:
939 if spkWrap.getUniqueId() == id:
940 return spkWrap
942 return None
944 def areSignatureCompleted(self):
945 value = True;
946 for spkWrap in self._speakerWrapperList:
947 if spkWrap.getStatus() != SpeakerStatusEnum.FROMFILE and \
948 spkWrap.getStatus() != SpeakerStatusEnum.SIGNED:
949 value = False;
951 return value
953 def getSpeakerWrapperListByStatus(self, status):
954 '''Return a list of SpeakerWrapper matching the status.
956 list = []
957 for spkWrap in self._speakerWrapperList:
958 if spkWrap.getStatus() == status:
959 list.append(spkWrap)
961 return list
963 def getSpeakerEmailByUniqueId(self, id, user):
964 ''' Return the email of a speaker according to the uniqueId.
965 id: uniqueId of the speaker wrapper.
966 user: user object of the sender of the emails, in order to check the rights.
969 canManageRequest = CollaborationTools.getRequestTypeUserCanManage(self._conf, user)
970 requestTypeAccepted = ""
972 if canManageRequest == "recording":
973 requestTypeAccepted = ["recording"]
974 elif canManageRequest == "webcast":
975 requestTypeAccepted = ["webcast"]
976 elif canManageRequest == "both":
977 requestTypeAccepted = ["recording", "webcast", "both"]
979 list = []
980 for spkWrap in self._speakerWrapperList:
981 if spkWrap.getUniqueId() == id and \
982 spkWrap.hasEmail() and spkWrap.getStatus() not in \
983 [SpeakerStatusEnum.SIGNED, SpeakerStatusEnum.FROMFILE] and \
984 spkWrap.getRequestType() in requestTypeAccepted:
986 list.append(spkWrap.getObject().getEmail())
988 return list
990 def addVideoService(self, uniqueId, videoService):
991 """ Adds a video service to Contribution / Session link in the tracking
992 dictionary in order {uniqueId : videoService}
994 if self.getVideoServices().has_key(uniqueId):
995 self.getVideoServices()[uniqueId].append(videoService)
996 else:
997 self.getVideoServices()[uniqueId] = [videoService]
999 def removeVideoAllServices(self, uniqueId):
1000 """ Removes all associations of Contributions / Sessions with video
1001 services from the dictionary, key included.
1004 if not self.hasVideoService(uniqueId):
1005 return None
1007 del self.getVideoServices()[uniqueId]
1009 def removeVideoSingleService(self, uniqueId, videoService):
1010 """ Removes a specific video service from a specific contribution. As
1011 the list of services is unordered, iterate through to match for
1012 removal - performance cost therefore occurs here.
1015 if not self.hasVideoService(uniqueId):
1016 return None
1018 target = self.getVideoServicesById(uniqueId)
1020 for service in target:
1021 if service == videoService:
1022 target.remove(service)
1023 break
1025 # There are no more entries, therefore remove the dictionary entry too.
1026 if len(target) == 0:
1027 self.removeVideoAllServices(uniqueId)
1029 def getVideoServices(self):
1030 """ Returns the OOBTree associating event unique IDs with the List
1031 of video services associated.
1034 if not hasattr(self, "_bookingsToVideoServices"):
1035 self._bookingsToVideoServices = OOBTree()
1037 return self._bookingsToVideoServices
1039 def getVideoServicesById(self, uniqueId):
1040 """ Returns a list of video services associated with the uniqueId
1041 for printing in event timetable. Returns None if no video services
1042 are found.
1045 if not self.hasVideoService(uniqueId):
1046 return None
1048 return self.getVideoServices()[uniqueId]
1050 def hasVideoService(self, uniqueId, service=None):
1051 """ Returns True if the uniqueId of the Contribution or Session provided
1052 has an entry in the self._bookingsToVideoServices dictionary, thusly
1053 denoting the presence of linked bookings. Second parameter is for more
1054 specific matching, i.e. returns True if unique ID is associated with
1055 specific service.
1057 if service is None:
1058 return self.getVideoServices().has_key(uniqueId)
1060 if self.getVideoServices().has_key(uniqueId):
1061 for serv in self.getVideoServicesById(uniqueId):
1062 if serv == service:
1063 return True
1064 else:
1065 return self.getVideoServices().has_key(uniqueId)
1067 def isAnyRequestAccepted(self):
1069 Return true if at least one between recording and webcast request
1070 has been accepted.
1072 value = False
1073 rr = self.getSingleBooking("RecordingRequest")
1074 wr = self.getSingleBooking("WebcastRequest")
1076 if rr:
1077 value = rr.getAcceptRejectStatus()
1079 if wr:
1080 value = value or wr.getAcceptRejectStatus()
1082 return value
1084 def isContributionReadyToBePublished(self, contId):
1085 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
1086 self.updateSpeakerWrapperList(True)
1088 exists = False
1089 for spkWrap in self._speakerWrapperList:
1090 if spkWrap.getContId() == contId:
1091 exists = True
1092 if spkWrap.getStatus() != SpeakerStatusEnum.SIGNED and \
1093 spkWrap.getStatus() != SpeakerStatusEnum.FROMFILE:
1094 return False
1096 #The list has to have at least one spkWrap with the given contId
1097 return exists
1099 class CSBookingBase(Persistent, Fossilizable):
1100 fossilizes(ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil)
1102 """ Base class that represents a Collaboration Systems booking.
1103 Every Collaboration plugin will have to implement this class.
1104 In the base class are gathered all the functionalities / elements that are common for all plugins.
1105 A booking is Persistent (DateChangeObserver inherits from Persistent) so it will be stored in the database.
1106 Also, every CSBookingBase object in the server will be mirrored by a Javascript object in the client, through "Pickling".
1108 Every class that implements the CSBookingBase has to declare the following class attributes:
1109 _hasStart : True if the plugin has a "start" concept. Otherwise, the "start" button will not appear, etc.
1110 _hasStop : True if the plugin has a "stop" concept. Otherwise, the "stop" button will not appear, etc.
1111 _hasConnect : True if the plugin has a "connect" concept. Otherwise, the "connect" button will not appear, etc.
1112 _hasCheckStatus: True if the plugin has a "check status" concept. Otherwise, the "check status" button will not appear, etc.
1113 _hasAcceptReject: True if the plugin has a "accept or reject" concept. Otherwise, the "accept" and "reject" buttons will not appear, etc.
1114 _requiresServerCallForStart : True if we should notify the server when the user presses the "start" button.
1115 _requiresServerCallForStop : True if we should notify the server when the user presses the "stop" button.
1116 _requiresServerCallForConnect : True if we should notify the server when the user presses the "connect" button.
1117 _requiresClientCallForStart : True if the browser should execute some JS action when the user presses the "start" button.
1118 _requiresClientCallForStop : True if the browser should execute some JS action when the user presses the "stop" button.
1119 _requiresClientCallForConnect : True if the browser should execute some JS action when the user presses the "connect" button.
1120 _needsBookingParamsCheck : True if the booking parameters should be checked after the booking is added / edited.
1121 If True, the _checkBookingParams method will be called by the setBookingParams method.
1122 _needsToBeNotifiedOnView: True if the booking object needs to be notified (through the "notifyOnView" method)
1123 when the user "sees" the booking, for example when returning the list of bookings.
1124 _canBeNotifiedOfEventDateChanges: True if bookings of this type should be able to be notified
1125 of their owner Event changing start date, end date or timezone.
1126 _allowMultiple: True if this booking type allows more than 1 booking per event.
1129 _hasStart = False
1130 _hasStop = False
1131 _hasConnect = False
1132 _hasCheckStatus = False
1133 _hasAcceptReject = False
1134 _hasStartStopAll = False
1135 _requiresServerCallForStart = False
1136 _requiresServerCallForStop = False
1137 _requiresServerCallForConnect = False
1138 _requiresClientCallForStart = False
1139 _requiresClientCallForStop = False
1140 _requiresClientCallForConnect = False
1141 _needsBookingParamsCheck = False
1142 _needsToBeNotifiedOnView = False
1143 _canBeNotifiedOfEventDateChanges = True
1144 _allowMultiple = True
1145 _shouldBeIndexed = True
1146 _commonIndexes = []
1147 _hasStartDate = True
1148 _hasEventDisplay = False
1149 _hasTitle = False
1150 _adminOnly = False
1151 _complexParameters = []
1152 _linkVideoType = None
1153 _linkVideoId = None
1155 def __init__(self, bookingType, conf):
1156 """ Constructor for the CSBookingBase class.
1157 id: a string with the id of the booking
1158 bookingType: a string with the type of the booking. Example: "DummyPlugin", "EVO"
1159 conf: a Conference object to which this booking belongs (through the CSBookingManager object). The meeting of this booking.
1160 startTime: TODO
1161 endTime: TODO
1163 Other attributes initialized by this constructor:
1164 -_bookingParams: the parameters necessary to perform the booking.
1165 The plugins will decide if the booking gets authorized or not depending on this.
1166 Needs to be defined by the implementing class, as keys with empty values.
1167 -_startingParams: the parameters necessary to start the booking.
1168 They will be used on the client for the local start action.
1169 Needs to be defined by the implementing class, as keys with empty values.
1170 -_warning: A warning is a plugin-defined object, with information to show to the user when
1171 the operation went well but we still have to show some info to the user.
1172 -_permissionToStart : Even if the "start" button for a booking is able to be pushed, there may be cases where the booking should
1173 not start. For example, if it's not the correct time yet.
1174 In that case "permissionToStart" should be set to false so that the booking doesn't start.
1175 -_permissionToStop: Same as permissionToStart. Sometimes the booking should not be allowed to stop even if the "stop" button is available.
1176 -_permissionToConnect: Same as permissionToStart. Sometimes the booking should not be allowed to connect even if the "connect" button is available.
1178 self._id = None
1179 self._type = bookingType
1180 self._plugin = CollaborationTools.getPlugin(self._type)
1181 self._conf = conf
1182 self._warning = None
1183 self._creationDate = nowutc()
1184 self._modificationDate = nowutc()
1185 self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
1186 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
1187 self._startDate = None
1188 self._endDate = None
1189 self._startDateTimestamp = None
1190 self._endDateTimestamp = None
1191 self._acceptRejectStatus = None #None = not yet accepted / rejected; True = accepted; False = rejected
1192 self._rejectReason = ""
1193 self._bookingParams = {}
1194 self._canBeDeleted = True
1195 self._permissionToStart = False
1196 self._permissionToStop = False
1197 self._permissionToConnect = False
1198 self._needsToBeNotifiedOfDateChanges = self._canBeNotifiedOfEventDateChanges
1199 self._hidden = False
1200 self._play_status = None
1202 setattr(self, "_" + bookingType + "Options", CollaborationTools.getPlugin(bookingType).getOptions())
1203 #NOTE: Should maybe notify the creation of a new booking, specially if it's a single booking
1204 # like that can update requestType of the speaker wrapper...
1206 def getId(self):
1207 """ Returns the internal, per-conference id of the booking.
1208 This attribute will be available in Javascript with the "id" identifier.
1210 return self._id
1212 def setId(self, id):
1213 """ Sets the internal, per-conference id of the booking
1215 self._id = id
1217 def getUniqueId(self):
1218 """ Returns an unique Id that identifies this booking server-wide.
1219 Useful for ExternalOperationsManager
1221 return "%scsbook%s" % (self.getConference().getUniqueId(), self.getId())
1223 def getType(self):
1224 """ Returns the type of the booking, as a string: "EVO", "DummyPlugin"
1225 This attribute will be available in Javascript with the "type" identifier.
1227 return self._type
1229 def getConference(self):
1230 """ Returns the owner of this CSBookingBase object, which is a Conference object representing the meeting.
1232 return self._conf
1234 def setConference(self, conf):
1235 """ Sets the owner of this CSBookingBase object, which is a Conference object representing the meeting.
1237 self._conf = conf
1239 def getWarning(self):
1240 """ Returns a warning attached to this booking.
1241 A warning is a plugin-defined object, with information to show to the user when
1242 the operation went well but we still have to show some info to the user.
1243 To be overloaded by plugins.
1245 if not hasattr(self, '_warning'):
1246 self._warning = None
1247 return self._warning
1249 def setWarning(self, warning):
1250 """ Sets a warning attached to this booking.
1251 A warning is a plugin-defined object, with information to show to the user when
1252 the operation went well but we still have to show some info to the user.
1253 To be overloaded by plugins.
1255 self._warning = warning
1257 def getCreationDate(self):
1258 """ Returns the date this booking was created, as a timezone localized datetime object
1260 if not hasattr(self, "_creationDate"): #TODO: remove when safe
1261 self._creationDate = nowutc()
1262 return self._creationDate
1264 def getAdjustedCreationDate(self, tz=None):
1265 """ Returns the booking creation date, adjusted to a given timezone.
1266 If no timezone is provided, the event's timezone is used
1268 return getAdjustedDate(self.getCreationDate(), self.getConference(), tz)
1270 def getCreationDateTimestamp(self):
1271 if not hasattr(object, "_creationDateTimestamp"): #TODO: remove when safe
1272 self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
1273 return self._creationDateTimestamp
1275 def getModificationDate(self):
1276 """ Returns the date this booking was modified last
1278 if not hasattr(self, "_modificationDate"): #TODO: remove when safe
1279 self._modificationDate = nowutc()
1280 return self._modificationDate
1282 def getAdjustedModificationDate(self, tz=None):
1283 """ Returns the booking last modification date, adjusted to a given timezone.
1284 If no timezone is provided, the event's timezone is used
1286 return getAdjustedDate(self.getModificationDate(), self.getConference(), tz)
1288 def getModificationDateTimestamp(self):
1289 if not hasattr(object, "_modificationDateTimestamp"): #TODO: remove when safe
1290 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
1291 return self._modificationDateTimestamp
1293 def setModificationDate(self, date):
1294 """ Sets the date this booking was modified last
1296 self._modificationDate = date
1297 if date:
1298 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(date))
1299 else:
1300 self._modificationDateTimestamp = None
1302 def getBookingsOfSameType(self, sorted = False):
1303 """ Returns a list of the bookings of the same type as this one (including this one)
1304 sorted: if true, bookings will be sorted by id
1306 return self._conf.getCSBookingManager().getBookingList(sorted, self._type)
1308 def getPlugin(self):
1309 """ Returns the Plugin object associated to this booking.
1311 return self._plugin
1313 def setPlugin(self, plugin):
1314 """ Sets the Plugin object associated to this booking.
1316 self._plugin = plugin
1318 def getPluginOptions(self):
1319 """ Utility method that returns the plugin options for this booking's type of plugin
1321 return self._plugin.getOptions()
1323 def getPluginOptionByName(self, optionName):
1324 """ Utility method that returns a plugin option, given its name, for this booking's type of plugin
1326 return self.getPluginOptions()[optionName]
1328 def getStartDate(self):
1329 """ Returns the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1331 return self._startDate
1333 def getAdjustedStartDate(self, tz=None):
1334 """ Returns the booking start date, adjusted to a given timezone.
1335 If no timezone is provided, the event's timezone is used
1337 if self.getStartDate():
1338 return getAdjustedDate(self.getStartDate(), self.getConference(), tz)
1339 else:
1340 return None
1342 def getStartDateTimestamp(self):
1343 if not hasattr(object, "_startDateTimestamp"): #TODO: remove when safe
1344 self._startDateTimestamp = int(datetimeToUnixTimeInt(self._startDate))
1345 return self._startDateTimestamp
1347 def setStartDateTimestamp(self, startDateTimestamp):
1348 self._startDateTimestamp = startDateTimestamp
1350 def getStartDateAsString(self):
1351 """ Returns the start date as a string, expressed in the meeting's timezone
1353 if self._startDate == None:
1354 return ""
1355 else:
1356 return formatDateTime(self.getAdjustedStartDate(), locale='en_US')
1358 def setStartDate(self, startDate):
1359 """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1361 self._startDate = startDate
1362 if startDate:
1363 self._startDateTimestamp = int(datetimeToUnixTimeInt(startDate))
1364 else:
1365 self._startDateTimestamp = None
1367 def setStartDateFromString(self, startDateString):
1368 """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
1370 if startDateString == "":
1371 self.setStartDate(None)
1372 else:
1373 try:
1374 self.setStartDate(setAdjustedDate(parseDateTime(startDateString), self._conf))
1375 except ValueError:
1376 raise CollaborationServiceException("startDate parameter (" + startDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
1378 def getEndDate(self):
1379 """ Returns the end date as an datetime object with timezone information (adjusted to the meeting's timezone)
1381 return self._endDate
1383 def isHappeningNow(self):
1384 now = nowutc()
1385 return self.getStartDate() < now and self.getEndDate() > now
1387 def hasHappened(self):
1388 now = nowutc()
1389 return now > self.getEndDate()
1391 def getAdjustedEndDate(self, tz=None):
1392 """ Returns the booking end date, adjusted to a given timezone.
1393 If no timezone is provided, the event's timezone is used
1395 return getAdjustedDate(self.getEndDate(), self.getConference(), tz)
1397 def getEndDateTimestamp(self):
1398 if not hasattr(object, "_endDateTimestamp"): #TODO: remove when safe
1399 self._endDateTimestamp = int(datetimeToUnixTimeInt(self._endDate))
1400 return self._endDateTimestamp
1402 def setEndDateTimestamp(self, endDateTimestamp):
1403 self._endDateTimestamp = endDateTimestamp
1405 def getEndDateAsString(self):
1406 """ Returns the start date as a string, expressed in the meeting's timezone
1408 if self._endDate == None:
1409 return ""
1410 else:
1411 return formatDateTime(self.getAdjustedEndDate(), locale='en_US')
1413 def setEndDate(self, endDate):
1414 """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1416 self._endDate = endDate
1417 if endDate:
1418 self._endDateTimestamp = int(datetimeToUnixTimeInt(endDate))
1419 else:
1420 self._endDateTimestamp = None
1422 def setEndDateFromString(self, endDateString):
1423 """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
1425 if endDateString == "":
1426 self.setEndDate(None)
1427 else:
1428 try:
1429 self.setEndDate(setAdjustedDate(parseDateTime(endDateString), self._conf))
1430 except ValueError:
1431 raise CollaborationServiceException("endDate parameter (" + endDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
1433 def getStatusMessage(self):
1434 """ Returns the status message as a string.
1435 This attribute will be available in Javascript with the "statusMessage"
1437 status = self.getPlayStatus()
1438 if status == None:
1439 if self.isHappeningNow():
1440 return _("Ready to start!")
1441 elif self.hasHappened():
1442 return _("Already took place")
1443 else:
1444 return _("Booking created")
1445 elif status:
1446 return _("Conference started")
1447 elif not status:
1448 return _("Conference stopped")
1450 def getStatusClass(self):
1451 """ Returns the status message CSS class as a string.
1452 This attribute will be available in Javascript with the "statusClass"
1454 if self.getPlayStatus() == None or self.hasHappened():
1455 return "statusMessageOther"
1456 else:
1457 return "statusMessageOK"
1459 def accept(self, user = None):
1460 """ Sets this booking as accepted
1462 self._acceptRejectStatus = True
1463 self._accept(user)
1465 def reject(self, reason):
1466 """ Sets this booking as rejected, and stores the reason
1468 self._acceptRejectStatus = False
1469 self._rejectReason = reason
1470 self._reject()
1472 def clearAcceptRejectStatus(self):
1473 """ Sets back the accept / reject status to None
1475 self._acceptRejectStatus = None
1477 def getAcceptRejectStatus(self):
1478 """ Returns the Accept/Reject status of the booking
1479 This attribute will be available in Javascript with the "acceptRejectStatus"
1480 Its value will be:
1481 -None if the booking has not been accepted or rejected yet,
1482 -True if it has been accepted,
1483 -False if it has been rejected
1485 if not hasattr(self, "_acceptRejectStatus"):
1486 self._acceptRejectStatus = None
1487 return self._acceptRejectStatus
1489 def getRejectReason(self):
1490 """ Returns the rejection reason.
1491 This attribute will be available in Javascript with the "rejectReason"
1493 if not hasattr(self, "_rejectReason"):
1494 self._rejectReason = ""
1495 return self._rejectReason
1497 ## methods relating to the linking of CSBooking objects to Contributions & Sessions
1499 def hasSessionOrContributionLink(self):
1500 return (self.isLinkedToContribution() or self.isLinkedToSession())
1502 def isLinkedToSession(self):
1503 return (self._linkVideoType == "session")
1505 def isLinkedToContribution(self):
1506 return (self._linkVideoType == "contribution")
1508 def getLinkId(self):
1509 """ Returns the unique ID of the Contribution or Session which this
1510 object is associated with, completely agnostic of the link type.
1511 Returns None if no association (default) found.
1514 return self._linkVideoId
1516 def getLinkIdDict(self):
1517 """ Returns a dictionary of structure linkType (session | contribution)
1518 : unique ID of referenced object.
1519 Returns None if no association is found.
1521 linkId = self.getLinkId()
1523 if linkId == None:
1524 return linkId
1526 return {self._linkVideoType : linkId}
1528 def getLinkType(self):
1529 """ Returns a string denoting the link type, that is whether linked
1530 to a session or contribution.
1533 return self._linkVideoType
1535 def setLinkType(self, linkDict):
1536 """ Accepts a dictionary of linkType: linkId """
1538 # case of non-linked bookings
1539 if linkDict is None:
1540 return
1542 self._linkVideoType = linkDict.keys()[0]
1543 self._linkVideoId = linkDict.values()[0]
1545 def resetLinkParams(self):
1546 """ Removes all association with a Session or Contribution from this
1547 CSBooking only.
1550 self._linkVideoType = self._linkVideoId = None
1552 def getLocation(self):
1553 return self._conf.getLocation().getName() if self._conf.getLocation() else ""
1555 def getRoom(self):
1556 return self._conf.getRoom().getName() if self._conf.getRoom() else ""
1558 def getBookingParams(self):
1559 """ Returns a dictionary with the booking params.
1560 This attribute will be available in Javascript with the "bookingParams"
1562 If self._bookingParams has not been set by the implementing class, an exception is thrown.
1564 Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1565 be retrieved through getter methods.
1566 If a subclass defines a class attributes called _complexParameters (a list of strings),
1567 parameter names that are in this list will also be included in the returned dictionary.
1568 Their value will be retrieved by calling the corresponding getXXX methods
1569 but instead the inheriting class's setXXX method will be called.
1570 Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] correspond
1571 to the methods getCommunityName, getAccessPassword, getHasAccessPassword.
1572 If you include a parameter in the _complexParameters list, you always have to implement the corresponding getter method.
1574 bookingParams = {}
1575 for k, v in self.__class__._simpleParameters.iteritems():
1576 if k in self._bookingParams:
1577 value = self._bookingParams[k]
1578 else:
1579 value = v[1] #we use the default value
1580 if v[0] is bool and value is True: #we assume it will be used in a single checkbox
1581 value = ["yes"]
1582 if value is not False: #we do not include False, it means the single checkbox is not checked
1583 bookingParams[k] = value
1585 if hasattr(self.__class__, "_complexParameters") and len(self.__class__._complexParameters) > 0:
1586 getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1587 for paramName in self.__class__._complexParameters:
1588 getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1589 if getMethodName in getterMethods:
1590 bookingParams[paramName] = getterMethods[getMethodName]()
1591 else:
1592 raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1594 bookingParams["startDate"] = self.getStartDateAsString()
1595 bookingParams["endDate"] = self.getEndDateAsString()
1596 if self.needsToBeNotifiedOfDateChanges():
1597 bookingParams["notifyOnDateChanges"] = ["yes"]
1598 if self.isHidden():
1599 bookingParams["hidden"] = ["yes"]
1600 return bookingParams
1603 def getBookingParamByName(self, paramName):
1604 if paramName in self.__class__._simpleParameters:
1605 if not paramName in self._bookingParams:
1606 self._bookingParams[paramName] = self.__class__._simpleParameters[paramName][1]
1607 return self._bookingParams[paramName]
1608 elif hasattr(self.__class__, "_complexParameters") and paramName in self.__class__._complexParameters:
1609 getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1610 getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1611 if getMethodName in getterMethods:
1612 return getterMethods[getMethodName]()
1613 else:
1614 raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1615 else:
1616 raise CollaborationServiceException("Tried to retrieve parameter " + str(paramName) + " but this parameter does not exist")
1618 def getContributionSpeakerSingleBooking(self):
1619 ''' Return a dictionnary with the contributions and their speakers that need to be recorded
1620 e.g: {contId:[Spk1Object, Spk2Object, Spk3Object], cont2:[Spk1Object]}...
1622 request = {}
1624 recordingTalksChoice = self.getBookingParams()["talks"] #either "all", "choose" or ""
1625 listTalksToRecord = self.getBookingParams()["talkSelection"]
1627 if self._conf.getType() == "simple_event":
1628 request[self._conf.getId()] = []
1629 for chair in self._conf.getChairList():
1630 request[self._conf.getId()].append(chair)
1631 else:
1632 for cont in self._conf.getContributionList():
1633 ''' We select the contributions that respect the following conditions:
1634 - They have Speakers assigned.
1635 - They are scheduled. (to discuss...)
1636 - They have been chosen for the recording request.
1638 if recordingTalksChoice != "choose" or cont.getId() in listTalksToRecord:
1639 if cont.isScheduled():
1640 request[cont.getId()] = []
1641 for spk in cont.getSpeakerList():
1642 request[cont.getId()].append(spk)
1644 return request
1646 def setBookingParams(self, params):
1647 """ Sets new booking parameters.
1648 params: a dict with key/value pairs with the new values for the booking parameters.
1649 If the plugin's _needsBookingParamsCheck is True, the _checkBookingParams() method will be called.
1650 This function will return False if all the checks were OK or if there were no checks, and otherwise will throw
1651 an exception or return a CSReturnedErrorBase error.
1653 Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1654 be set through setter methods.
1655 If a subclass defines a class attributes called _complexParameters (a list of strings),
1656 parameter names that are in 'params' and also in this list will not be assigned directly,
1657 but instead the inheriting class's setXXX method will be called.
1659 Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] corresponds
1660 to methods setCommunityName, setAccessPassword, setHasAccessPassword.
1661 Note: even if a parameter is in this list, you can decide not to implement its corresponding set
1662 method if you never expect the parameter name to come up inside 'params'.
1665 sanitizeResult = self.sanitizeParams(params)
1666 if sanitizeResult:
1667 return sanitizeResult
1669 self.setHidden(params.pop("hidden", False) == ["yes"])
1670 self.setNeedsToBeNotifiedOfDateChanges(params.pop("notifyOnDateChanges", False) == ["yes"])
1672 startDate = params.pop("startDate", None)
1673 if startDate is not None:
1674 self.setStartDateFromString(startDate)
1675 endDate = params.pop("endDate", None)
1676 if endDate is not None:
1677 self.setEndDateFromString(endDate)
1679 for k,v in params.iteritems():
1680 if k in self.__class__._simpleParameters:
1681 if self.__class__._simpleParameters[k][0]:
1682 try:
1683 v = self.__class__._simpleParameters[k][0](v)
1684 except ValueError:
1685 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")
1686 self._bookingParams[k] = v
1687 elif k in self.__class__._complexParameters:
1688 setterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('set')))
1689 setMethodName = 'set' + k[0].upper() + k[1:]
1690 if setMethodName in setterMethods:
1691 setterMethods[setMethodName](v)
1692 else:
1693 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")
1694 else:
1695 raise CollaborationServiceException("Tried to set the value of a parameter with name " + str(k) + " that was not declared")
1697 for k, v in self.__class__._simpleParameters.iteritems():
1698 if not k in self._bookingParams:
1699 self._bookingParams[k] = self.__class__._simpleParameters[k][1]
1701 if self.needsBookingParamsCheck():
1702 return self._checkBookingParams()
1704 return False
1706 def sanitizeParams(self, params):
1707 """ Checks if the fields introduced into the booking / request form
1708 have any kind of HTML or script tag.
1710 if not isinstance(params, dict):
1711 raise CollaborationServiceException("Booking parameters are not a dictionary")
1713 invalidFields = []
1714 for k, v in params.iteritems():
1715 if type(v) == str and hasTags(v):
1716 invalidFields.append(k)
1718 if invalidFields:
1719 return CSSanitizationError(invalidFields)
1720 else:
1721 return None
1723 def _getTypeDisplayName(self):
1724 return CollaborationTools.getXMLGenerator(self._type).getDisplayName()
1726 def _getFirstLineInfo(self, tz):
1727 return CollaborationTools.getXMLGenerator(self._type).getFirstLineInfo(self, tz)
1729 def _getTitle(self):
1730 if self.hasEventDisplay():
1731 raise CollaborationException("Method _getTitle was not overriden for the plugin type " + str(self._type))
1733 def _getInformationDisplay(self, tz):
1734 templateClass = CollaborationTools.getTemplateClass(self.getType(), "WInformationDisplay")
1735 if templateClass:
1736 return templateClass(self, tz).getHTML()
1737 else:
1738 return None
1740 def _getLaunchDisplayInfo(self):
1741 """ To be overloaded by plugins
1743 return None
1745 def _checkBookingParams(self):
1746 """ To be overriden by inheriting classes.
1747 Verifies that the booking parameters are correct. For example, that a numeric field is actually a number.
1748 Otherwise, an exception should be thrown.
1749 If there are no errors, the method should just return.
1751 if self.needsBookingParamsCheck():
1752 raise CollaborationServiceException("Method _checkBookingParams was not overriden for the plugin type " + str(self._type))
1754 def hasStart(self):
1755 """ Returns if this booking belongs to a plugin who has a "start" concept.
1756 This attribute will be available in Javascript with the "hasStart" attribute
1758 return self._hasStart
1760 def hasStartStopAll(self):
1761 """ Returns if this booking belongs to a plugin who has a "start" concept, and all of its bookings for a conference
1762 can be started simultanously.
1763 This attribute will be available in Javascript with the "hasStart" attribute
1765 return self._hasStartStopAll
1767 def hasStop(self):
1768 """ Returns if this booking belongs to a plugin who has a "stop" concept.
1769 This attribute will be available in Javascript with the "hasStop" attribute
1771 return self._hasStop
1773 def hasConnect(self):
1774 """ Returns if this booking belongs to a plugin who has a "connect" concept.
1775 This attribute will be available in Javascript with the "hasConnect" attribute
1777 if not hasattr(self, '_hasConnect'):
1778 self._hasConnect = False
1779 return self._hasConnect
1781 def hasCheckStatus(self):
1782 """ Returns if this booking belongs to a plugin who has a "check status" concept.
1783 This attribute will be available in Javascript with the "hasCheckStatus" attribute
1785 return self._hasCheckStatus
1787 def hasAcceptReject(self):
1788 """ Returns if this booking belongs to a plugin who has a "accept or reject" concept.
1789 This attribute will be available in Javascript with the "hasAcceptReject" attribute
1791 return self._hasAcceptReject
1793 def requiresServerCallForStart(self):
1794 """ Returns if this booking belongs to a plugin who requires a server call when the start button is pressed.
1795 This attribute will be available in Javascript with the "requiresServerCallForStart" attribute
1797 return self._requiresServerCallForStart
1799 def requiresServerCallForStop(self):
1800 """ Returns if this booking belongs to a plugin who requires a server call when the stop button is pressed.
1801 This attribute will be available in Javascript with the "requiresServerCallForStop" attribute
1803 return self._requiresServerCallForStop
1805 def requiresServerCallForConnect(self):
1806 """ Returns if this booking belongs to a plugin who requires a server call when the connect button is pressed.
1807 This attribute will be available in Javascript with the "requiresServerCallForConnect" attribute
1809 if not hasattr(self, '_requiresServerCallForConnect'):
1810 self._requiresServerCallForConnect = False
1811 return self._requiresServerCallForConnect
1813 def requiresClientCallForStart(self):
1814 """ Returns if this booking belongs to a plugin who requires a client call when the start button is pressed.
1815 This attribute will be available in Javascript with the "requiresClientCallForStart" attribute
1817 return self._requiresClientCallForStart
1819 def requiresClientCallForStop(self):
1820 """ Returns if this booking belongs to a plugin who requires a client call when the stop button is pressed.
1821 This attribute will be available in Javascript with the "requiresClientCallForStop" attribute
1823 return self._requiresClientCallForStop
1825 def requiresClientCallForConnect(self):
1826 """ Returns if this booking belongs to a plugin who requires a client call when the connect button is pressed.
1827 This attribute will be available in Javascript with the "requiresClientCallForConnect" attribute
1829 if not hasattr(self, '_requiresClientCallForConnect'):
1830 self._requiresClientCallForConnect = False
1831 return self._requiresClientCallForConnect
1833 def canBeDeleted(self):
1834 """ Returns if this booking can be deleted, in the sense that the "Remove" button will be active and able to be pressed.
1835 This attribute will be available in Javascript with the "canBeDeleted" attribute
1838 return self._canBeDeleted
1840 def setCanBeDeleted(self, canBeDeleted):
1841 """ Sets if this booking can be deleted, in the sense that the "Remove" button will be active and able to be pressed.
1842 This attribute will be available in Javascript with the "canBeDeleted" attribute
1844 self._canBeDeleted = canBeDeleted
1846 def canBeStarted(self):
1847 """ Returns if this booking can be started, in the sense that the "Start" button will be active and able to be pressed.
1848 This attribute will be available in Javascript with the "canBeStarted" attribute
1850 return self.isHappeningNow()
1852 def canBeStopped(self):
1853 """ Returns if this booking can be stopped, in the sense that the "Stop" button will be active and able to be pressed.
1854 This attribute will be available in Javascript with the "canBeStopped" attribute
1856 return self.isHappeningNow()
1858 def canBeConnected(self):
1859 """ Returns if this booking can be connected, in the sense that the "Connect" button will be active and able to be pressed.
1860 This attribute will be available in Javascript with the "canBeConnected" attribute
1862 return self.isHappeningNow()
1864 def isPermittedToStart(self):
1865 """ Returns if this booking is allowed to start, in the sense that it will be started after the "Start" button is pressed.
1866 For example a booking should not be permitted to start before a given time, even if the button is active.
1867 This attribute will be available in Javascript with the "isPermittedToStart" attribute
1869 return self._permissionToStart
1871 def isPermittedToStop(self):
1872 """ Returns if this booking is allowed to stop, in the sense that it will be started after the "Stop" button is pressed.
1873 This attribute will be available in Javascript with the "isPermittedToStop" attribute
1875 return self._permissionToStop
1877 def isPermittedToConnect(self):
1878 """ Returns if this booking is allowed to stop, in the sense that it will be connect after the "Connect" button is pressed.
1879 This attribute will be available in Javascript with the "isPermittedToConnect" attribute
1881 if not hasattr(self, '_permissionToConnect'):
1882 self._permissionToConnect = False
1883 return self._permissionToConnect
1885 def needsBookingParamsCheck(self):
1886 """ Returns if this booking belongs to a plugin that needs to verify the booking parameters.
1888 return self._needsBookingParamsCheck
1890 def needsToBeNotifiedOnView(self):
1891 """ Returns if this booking needs to be notified when someone views it (for example when the list of bookings is returned)
1893 return self._needsToBeNotifiedOnView
1895 def canBeNotifiedOfEventDateChanges(self):
1896 """ Returns if bookings of this type should be able to be notified
1897 of their owner Event changing start date, end date or timezone.
1899 return False
1901 def needsToBeNotifiedOfDateChanges(self):
1902 """ Returns if this booking in particular needs to be notified
1903 of their owner Event changing start date, end date or timezone.
1905 return self._needsToBeNotifiedOfDateChanges
1907 def setNeedsToBeNotifiedOfDateChanges(self, needsToBeNotifiedOfDateChanges):
1908 """ Sets if this booking in particular needs to be notified
1909 of their owner Event changing start date, end date or timezone.
1911 self._needsToBeNotifiedOfDateChanges = needsToBeNotifiedOfDateChanges
1913 def isHidden(self):
1914 """ Return if this booking is "hidden"
1915 A hidden booking will not appear in display pages
1917 if not hasattr(self, '_hidden'):
1918 self._hidden = False
1919 return self._hidden
1921 def setHidden(self, hidden):
1922 """ Sets if this booking is "hidden"
1923 A hidden booking will not appear in display pages
1924 hidden: a Boolean
1926 self._hidden = hidden
1928 def isAllowMultiple(self):
1929 """ Returns if this booking belongs to a type that allows multiple bookings per event.
1931 return self._allowMultiple
1933 def shouldBeIndexed(self):
1934 """ Returns if bookings of this type should be indexed
1936 return self._shouldBeIndexed
1938 def getCommonIndexes(self):
1939 """ Returns a list of strings with the names of the
1940 common (shared) indexes that bookings of this type want to
1941 be included in.
1943 return self._commonIndexes
1945 def index_instances(self):
1947 To be overloaded
1949 return
1951 def unindex_instances(self):
1953 To be overloaded
1955 return
1957 def index_talk(self, talk):
1959 To be overloaded
1961 return
1963 def unindex_talk(self, talk):
1965 To be overloaded
1967 return
1969 def getModificationURL(self):
1970 return urlHandlers.UHConfModifCollaboration.getURL(self.getConference(),
1971 secure = ContextManager.get('currentRH').use_https(),
1972 tab = CollaborationTools.getPluginTab(self.getPlugin()))
1974 def hasStartDate(self):
1975 """ Returns if bookings of this type have a start date
1976 (they may only have creation / modification date)
1978 return self._hasStartDate
1980 def hasTitle(self):
1981 """ Returns if bookings of this type have a title
1983 return self._hasTitle
1985 def hasEventDisplay(self):
1986 """ Returns if the type of this booking should display something on
1987 an event display page
1989 return self._hasEventDisplay
1991 def isAdminOnly(self):
1992 """ Returns if this booking / this booking's plugin pages should only be displayed
1993 to Server Admins, Video Service Admins, or the respective plugin admins.
1995 return self._adminOnly
1997 def _create(self):
1998 """ To be overriden by inheriting classes.
1999 This method is called when a booking is created, after setting the booking parameters.
2000 The plugin should decide if the booking is accepted or not.
2001 Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin,
2002 or a EVO HTTP server in the EVO case.
2004 raise CollaborationException("Method _create was not overriden for the plugin type " + str(self._type))
2006 def _modify(self, oldBookingParams):
2007 """ To be overriden by inheriting classes.
2008 This method is called when a booking is modifying, after setting the booking parameters.
2009 The plugin should decide if the booking is accepted or not.
2010 Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin
2011 or a EVO HTTP server in the EVO case.
2012 A dictionary with the previous booking params is passed. This dictionary is the one obtained
2013 by the method self.getBookingParams() before the new params input by the user are applied.
2015 raise CollaborationException("Method _modify was not overriden for the plugin type " + str(self._type))
2017 def _start(self):
2018 """ To be overriden by inheriting classes
2019 This method is called when the user presses the "Start" button in a plugin who has a "Start" concept
2020 and whose flag _requiresServerCallForStart is True.
2021 Often this will involve communication with another entity.
2023 if self.hasStart():
2024 raise CollaborationException("Method _start was not overriden for the plugin type " + str(self._type))
2025 else:
2026 pass
2028 def _stop(self):
2029 """ To be overriden by inheriting classes
2030 This method is called when the user presses the "Stop" button in a plugin who has a "Stop" concept
2031 and whose flag _requiresServerCallForStop is True.
2032 Often this will involve communication with another entity.
2034 if self.hasStop():
2035 raise CollaborationException("Method _stop was not overriden for the plugin type " + str(self._type))
2036 else:
2037 pass
2039 def _connect(self):
2040 """ To be overriden by inheriting classes
2041 This method is called when the user presses the "Connect" button in a plugin who has a "Connect" concept
2042 and whose flag _requiresServerCallForConnect is True.
2043 Often this will involve communication with another entity.
2045 if self.hasConnect():
2046 raise CollaborationException("Method _connect was not overriden for the plugin type " + str(self._type))
2047 else:
2048 pass
2050 def _checkStatus(self):
2051 """ To be overriden by inheriting classes
2052 This method is called when the user presses the "Check Status" button in a plugin who has a "check status" concept.
2053 Often this will involve communication with another entity.
2055 if self.hasCheckStatus():
2056 raise CollaborationException("Method _checkStatus was not overriden for the plugin type " + str(self._type))
2057 else:
2058 pass
2060 def _accept(self, user = None):
2061 """ To be overriden by inheriting classes
2062 This method is called when a user with privileges presses the "Accept" button
2063 in a plugin who has a "accept or reject" concept.
2064 Often this will involve communication with another entity.
2066 if self.hasAcceptReject():
2067 raise CollaborationException("Method _accept was not overriden for the plugin type " + str(self._type))
2068 else:
2069 pass
2071 def _reject(self):
2072 """ To be overriden by inheriting classes
2073 This method is called when a user with privileges presses the "Reject" button
2074 in a plugin who has a "accept or reject" concept.
2075 Often this will involve communication with another entity.
2077 if self.hasAcceptReject():
2078 raise CollaborationException("Method _reject was not overriden for the plugin type " + str(self._type))
2079 else:
2080 pass
2082 def _notifyOnView(self):
2083 """ To be overriden by inheriting classes
2084 This method is called when a user "sees" a booking, for example when the list of bookings is displayed.
2085 Maybe in this moment the booking wants to update its status.
2087 if self.needsToBeNotifiedOnView():
2088 raise CollaborationException("Method _notifyOnView was not overriden for the plugin type " + str(self._type))
2089 else:
2090 pass
2092 def _delete(self):
2093 """ To be overriden by inheriting classes
2094 This method is called whent he user removes a booking. Maybe the plugin will need to liberate
2095 ressources that were allocated to it.
2096 This method does not unregister the booking from the list of date change observer of the meeting
2098 raise CollaborationException("Method _delete was not overriden for the plugin type " + str(self._type))
2100 def _sendNotifications(self, operation):
2102 Sends a mail, wrapping it with ExternalOperationsManager
2104 ExternalOperationsManager.execute(self, "sendMail_" + operation, self._sendMail, operation)
2106 def _sendMail(self, operation):
2107 if operation == 'new':
2108 try:
2109 notification = mail.NewBookingNotification(self)
2110 GenericMailer.sendAndLog(notification, self._conf,
2111 "MaKaC/plugins/Collaboration/base.py",
2112 self._conf.getCreator())
2113 except Exception, e:
2114 Logger.get('VideoServ').error(
2115 """Could not send NewBookingNotification for booking with id %s of event with id %s, exception: %s""" %
2116 (self.getId(), self._conf.getId(), str(e)))
2117 raise
2119 elif operation == 'modify':
2120 try:
2121 notification = mail.BookingModifiedNotification(self)
2122 GenericMailer.sendAndLog(notification, self._conf,
2123 "MaKaC/plugins/Collaboration/base.py",
2124 self._conf.getCreator())
2125 except Exception, e:
2126 Logger.get('VideoServ').error(
2127 """Could not send BookingModifiedNotification for booking with id %s of event with id %s, exception: %s""" %
2128 (self.getId(), self._conf.getId(), str(e)))
2129 raise
2131 elif operation == 'remove':
2132 try:
2133 notification = mail.BookingDeletedNotification(self)
2134 GenericMailer.sendAndLog(notification, self._conf,
2135 "MaKaC/plugins/Collaboration/base.py",
2136 self._conf.getCreator())
2137 except Exception, e:
2138 Logger.get('VideoServ').error(
2139 """Could not send BookingDeletedNotification for booking with id %s of event with id %s, exception: %s""" %
2140 (self.getId(), self._conf.getId(), str(e)))
2141 raise
2143 def getPlayStatus(self):
2144 if not hasattr(self, '_play_status'):
2145 self._play_status = None
2146 return self._play_status
2148 """ Methods relating to the certain plugin architectures whereby talk
2149 selection is appropriate through the inheriting class' attributes.
2151 def hasTalkSelection(self):
2152 """ Some plugin types select individual contributions stored as a list
2153 of IDs in this parameter, returns param if this instance is one of them.
2155 return self._bookingParams.has_key('talkSelection')
2157 def _getTalkSelection(self):
2158 """ Returns the attribute if it is defined, None on error. """
2159 if self.hasTalkSelection():
2160 return self._bookingParams.get('talkSelection')
2161 return None
2163 def _hasTalkSelectionContent(self):
2164 """ If the talkSelection attribute is present and it has a quantity of
2165 items in its list greater than 0, individual talks have been chosen.
2167 ts = self._getTalkSelection()
2169 if ts is None:
2170 return False
2172 return len(ts) > 0
2174 def getTalkSelectionList(self):
2175 """ Returns the resultant list if it is present and populated. None if
2176 neither are true.
2178 if not self._hasTalkSelectionContent():
2179 return None
2181 return self._getTalkSelection()
2183 def _hasTalks(self):
2184 """ Returns the attribute if it is defined, None on error. """
2185 return self._bookingParams.has_key('talks')
2187 def isChooseTalkSelected(self):
2188 """ Returns if the talks are choosen"""
2189 if self._hasTalks():
2190 return self._bookingParams.get('talks') == "choose"
2191 else:
2192 return False
2194 def __cmp__(self, booking):
2195 return cmp(self.getUniqueId(), booking.getUniqueId()) if booking else 1
2197 class WCSTemplateBase(wcomponents.WTemplated):
2198 """ Base class for Collaboration templates.
2199 It stores the following attributes:
2200 _conf : the corresponding Conference object.
2201 _pluginName: the corresponding plugin ("EVO", "DummyPlugin", etc.).
2202 _XXXOptions: a dictionary whose values are the options of the plugin called pluginName.
2203 So, for example, if an EVO template inherits from this class, an attribute self._EVOOptions will be available.
2204 This class also overloads the _setTPLFile method so that Indico knows where each plugin's *.tpl files are.
2207 def __init__(self, pluginId):
2208 """ Constructor for the WCSTemplateBase class.
2209 conf: a Conference object
2210 plugin: the corresponding plugin
2212 self._plugin = CollaborationTools.getPlugin(pluginId)
2213 self._pluginId = self._plugin.getId()
2214 self._ph = PluginsHolder()
2216 setattr(self, "_" + self._pluginId + "Options", self._plugin.getOptions())
2218 def _setTPLFile(self, extension='tpl'):
2219 tplDir = os.path.join(self._plugin.getModule().__path__[0], "tpls")
2221 fname = "%s.%s" % (self.tplId, extension)
2222 self.tplFile = os.path.join(tplDir, fname)
2224 hfile = self._getSpecificTPL(os.path.join(tplDir,self._pluginId,'chelp'), self.tplId,extension='wohl')
2225 self.helpFile = os.path.join(tplDir,'chelp',hfile)
2228 class WCSPageTemplateBase(WCSTemplateBase):
2229 """ Base class for Collaboration templates for the create / modify booking form.
2232 def __init__(self, conf, pluginId, user):
2233 WCSTemplateBase.__init__(self, pluginId)
2234 self._conf = conf
2235 self._user = user
2238 class WJSBase(WCSTemplateBase):
2239 """ Base class for Collaboration templates for Javascript code template.
2240 It overloads _setTPLFile so that indico can find the Main.js, Extra.js and Indexing.js files.
2242 def __init__(self, conf, plugin, user):
2243 WCSTemplateBase.__init__(self, plugin)
2244 self._conf = conf
2245 self._user = user
2247 def _setTPLFile(self):
2248 WCSTemplateBase._setTPLFile(self, extension='js')
2249 self.helpFile = ''
2252 class WCSCSSBase(WCSTemplateBase):
2253 """ Base class for Collaboration templates for CSS code template
2254 It overloads _setTPLFile so that indico can find the style.css files.
2257 def _setTPLFile(self):
2258 tplDir = self._plugin.getModule().__path__[0]
2259 fname = "%s.css" % self.tplId
2260 self.tplFile = os.path.join(tplDir, fname)
2261 self.helpFile = ''
2264 class CSErrorBase(Fossilizable):
2265 fossilizes(ICSErrorBaseFossil)
2267 """ When _create, _modify or _remove want to return an error,
2268 they should return an error that inherits from this class
2271 def __init__(self):
2272 pass
2274 def getUserMessage(self):
2275 """ To be overloaded.
2276 Returns the string that will be shown to the user when this error will happen.
2278 raise CollaborationException("Method getUserMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
2280 def getLogMessage(self):
2281 """ To be overloaded.
2282 Returns the string that will be printed in Indico's log when this error will happen.
2284 raise CollaborationException("Method getLogMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
2286 class CSSanitizationError(CSErrorBase): #already Fossilizable
2287 fossilizes(ICSSanitizationErrorFossil)
2289 """ Class used to return which fields have a sanitization error (invalid html / script tags)
2292 def __init__(self, invalidFields):
2293 self._invalidFields = invalidFields
2295 def invalidFields(self):
2296 return self._invalidFields
2298 class CollaborationException(MaKaCError):
2299 """ Error for the Collaboration System "core". Each plugin should declare their own EVOError, etc.
2301 def __init__(self, msg, area = 'Collaboration', inner = None):
2302 MaKaCError.__init__(self, msg, area)
2303 self._inner = inner
2305 def getInner(self):
2306 return self._inner
2308 def __str__(self):
2309 return MaKaCError.__str__(self) + '. Inner: ' + str(self._inner)
2311 class CollaborationServiceException(ServiceError):
2312 """ Error for the Collaboration System "core", for Service calls.
2314 def __init__(self, message, inner = None):
2315 ServiceError.__init__(self, "ERR-COLL", message, inner)
2317 class SpeakerStatusEnum:
2318 (NOEMAIL, NOTSIGNED, SIGNED, FROMFILE, PENDING, REFUSED) = xrange(6)
2320 class SpeakerWrapper(Persistent, Fossilizable):
2322 fossilizes(ISpeakerWrapperBaseFossil)
2324 def __init__(self, speaker, contId, requestType):
2325 self.status = not speaker.getEmail() and SpeakerStatusEnum.NOEMAIL or SpeakerStatusEnum.NOTSIGNED
2326 self.speaker = speaker
2327 self.contId = contId
2328 self.requestType = requestType
2329 self.reason = ""
2330 self.localFile = None
2331 self.dateAgreement = 0
2332 self.ipSignature = None
2333 self.modificationDate = nowutc()
2334 self.uniqueIdHash = md5("%s.%s"%(time.time(), self.getUniqueId())).hexdigest()
2336 def getUniqueId(self):
2337 return "%s.%s"%(self.contId, self.speaker.getId())
2339 def getUniqueIdHash(self):
2340 # to remove once saved
2341 if not hasattr(self, "uniqueIdHash"):#TODO: remove when safe
2342 return md5(self.getUniqueId()).hexdigest()
2343 else:
2344 return self.uniqueIdHash
2346 def getStatus(self):
2347 return self.status
2349 def setStatus(self, newStatus, ip=None):
2350 try:
2351 self.status = newStatus;
2352 if newStatus == SpeakerStatusEnum.SIGNED or newStatus == SpeakerStatusEnum.FROMFILE:
2353 self.dateAgreement = now_utc()
2354 if newStatus == SpeakerStatusEnum.SIGNED:
2355 self.ipSignature = ip
2356 except Exception, e:
2357 Logger.get('VideoServ').error("Exception while changing the speaker status. Exception: " + str(e))
2359 def getDateAgreementSigned(self):
2360 if hasattr(self, "dateAgreement"):#TODO: remove when safe
2361 return self.dateAgreement
2362 return 0
2364 def getIpAddressWhenSigned(self):
2365 if hasattr(self, "ipSignature"):#TODO: remove when safe
2366 return self.ipSignature
2367 return None
2369 def getRejectReason(self):
2370 if hasattr(self, "reason"):#TODO: remove when safe
2371 if self.status == SpeakerStatusEnum.REFUSED and hasattr(self, "reason"):
2372 return self.reason
2373 else:
2374 return "This speaker has not refused the agreement."
2375 else:
2376 return "Information not available."
2378 def setRejectReason(self, reason):
2379 if hasattr(self, "reason"):#TODO: remove when safe
2380 self.reason = reason
2382 def getObject(self):
2383 return self.speaker
2385 def getContId(self):
2386 return self.contId
2388 def getRequestType(self):
2389 if hasattr(self, "requestType"):#TODO: remove when safe
2390 return self.requestType
2391 return "NA"
2393 def setRequestType(self, type):
2394 self.requestType = type
2396 def getSpeakerId(self):
2397 return self.speaker.getId()
2399 def getLocalFile(self):
2401 If exists, return path to paper agreement
2403 if hasattr(self, "localFile"):#TODO: remove when safe
2404 return self.localFile
2406 def setLocalFile(self, localFile):
2408 Set localFile of paper agreement
2410 if hasattr(self, "localFile"):#TODO: remove when safe
2411 self.localFile = localFile
2413 def hasEmail(self):
2414 if self.speaker.getEmail():
2415 return True
2416 return False
2418 def getCategory(self):
2419 return None
2421 def getConference(self):
2422 return self.speaker.getConference()
2424 def getContribution(self):
2425 # if the conference is a lecture, the getContribution will fail.
2426 if self.getConference().getType() == "simple_event":
2427 return None
2428 else:
2429 return self.speaker.getContribution()
2431 def getSession(self):
2432 return None
2434 def getSubContribution(self):
2435 return None
2437 def getModificationDate(self):
2438 if hasattr(self, "modificationDate"): # TODO: remove when safe
2439 return self.modificationDate
2440 return None
2442 def setModificationDate(self):
2443 if hasattr(self, "modificationDate"): # TODO: remove when safe
2444 self.modificationDate = now_utc()
2446 def getLocator(self):
2447 return self.getContribution().getLocator()
2449 def triggerNotification(self):
2450 if self.getRequestType() in ('recording', 'webcast'):
2451 self._triggerNotification(self.getRequestType())
2452 elif self.getRequestType() == 'both':
2453 self._triggerNotification('recording')
2454 self._triggerNotification('webcast')
2456 def _triggerNotification(self, type):
2457 url = None
2458 if type == 'recording':
2459 url = CollaborationTools.getOptionValue('RecordingRequest', 'AgreementNotificationURL')
2460 elif type == 'webcast':
2461 url = CollaborationTools.getOptionValue('WebcastRequest', 'AgreementNotificationURL')
2462 if not url:
2463 return
2464 signed = None
2465 if self.getStatus() in (SpeakerStatusEnum.FROMFILE, SpeakerStatusEnum.SIGNED):
2466 signed = True
2467 elif self.getStatus() == SpeakerStatusEnum.REFUSED:
2468 signed = False
2469 spk = self.getObject()
2470 payload = {
2471 'confId': self.getConference().getId(),
2472 'contrib': self.getContId(),
2473 'type': type,
2474 'status': self.getStatus(),
2475 'signed': signed,
2476 'speaker': {
2477 'id': spk.getId(),
2478 'name': spk.getFullName(),
2479 'email': spk.getEmail()
2482 cl = Client()
2483 cl.enqueue(HTTPTask(url, {'data': json.dumps(payload)}))