[FIX] Fix location room for connect
[cds-indico.git] / indico / MaKaC / plugins / Collaboration / base.py
blob2377e24807b02ecb75fdf81b3b4313b792c16846
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
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
51 from MaKaC.plugins.Collaboration.fossils import ICSErrorBaseFossil, ICSSanitizationErrorFossil,\
52 ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil,\
53 ISpeakerWrapperBaseFossil
56 class CSBookingManager(Persistent, Observer):
57 """ Class for managing the bookins of a meeting.
58 It will store the list of bookings. Adding / removing / editing bookings should be through this class.
59 """
61 _shouldBeTitleNotified = True
62 _shouldBeDateChangeNotified = True
63 _shouldBeLocationChangeNotified = True
64 _shouldBeDeletionNotified = True
66 def __init__(self, conf):
67 """ Constructor for the CSBookingManager class.
68 conf: a Conference object. The meeting that owns this CSBookingManager.
69 """
70 self._conf = conf
71 self._counter = Counter(1)
72 # a dict where the bookings will be stored. The key will be the booking id, the value a CSBookingBase object.
73 self._bookings = {}
75 # an index of bookings by type. The key will be a booking type (string), the value a list of booking id
76 self._bookingsByType = {}
78 # a list of ids with hidden bookings
79 self._hiddenBookings = set()
81 # an index of video services managers for each plugin. key: plugin name, value: list of users
82 self._managers = {}
84 # list of speaker wrapper for a conference
85 self._speakerWrapperList = []
86 self.updateSpeakerWrapperList()
88 def getOwner(self):
89 """ Returns the Conference (the meeting) that owns this CSBookingManager object.
90 """
91 return self._conf
93 def isCSAllowed(self, user = None):
94 """ Returns if the associated event should display a Video Services tab
95 This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
96 If a user is provided, we will take into account if the user can manage the plugin (for example,
97 an event manager cannot manage an admin-only plugin)
98 """
99 pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
100 if pluginsPerEventType:
101 for plugin in pluginsPerEventType[self._conf.getType()]:
102 if plugin.isActive() and (user is None or CollaborationTools.canUserManagePlugin(self._conf, plugin, user)):
103 return True
104 return False
106 def getAllowedPlugins(self):
107 """ Returns a list of allowed plugins (Plugin objects) for this event.
108 Only active plugins are returned.
109 This can depend on the kind of event (meeting, lecture, conference), on the equipment of the room...
111 pluginsPerEventType = CollaborationTools.getCollaborationPluginType().getOption("pluginsPerEventType").getValue()
112 if pluginsPerEventType is not None:
113 allowedForThisEvent = pluginsPerEventType[self._conf.getType()]
114 return [plugin for plugin in allowedForThisEvent if plugin.isActive()]
117 def getBookingList(self, sorted = False, filterByType = None, notify = False, onlyPublic = False):
118 """ Returns a list of all the bookings.
119 If sorted = True, the list of bookings will be sorted by id.
120 If filterByType = None, all bookings are returned.
121 Otherwise, just those of the type "filterByType" if filterByType is a string,
122 or if it is a list of strings, those who have a type included in filterByType.
125 if not hasattr(self, "_bookingsByType"): #TODO: remove when safe
126 self._bookingsByType = {}
128 if filterByType is not None:
129 if type(filterByType) == str:
130 keys = self._bookingsByType.get(filterByType, [])
131 if type(filterByType) == list:
132 keys = []
133 for pluginName in filterByType:
134 keys.extend(self._bookingsByType.get(pluginName, []))
135 else:
136 keys = self._bookings.keys()
138 if onlyPublic and self.getHiddenBookings():
139 keys = set(keys)
140 keys = keys.difference(self.getHiddenBookings())
141 keys = list(keys)
143 if sorted:
144 keys.sort(key = lambda k: int(k))
146 bookingList = [self._bookings[k] for k in keys]
148 #we notify all the bookings that they have been viewed. If a booking doesn't need to be viewed, nothing will happen
149 if notify:
150 for booking in bookingList:
151 if booking.needsToBeNotifiedOnView():
152 try:
153 booking._notifyOnView()
154 except Exception, e:
155 Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
157 return bookingList
159 def getBooking(self, id):
160 """ Returns a booking given its id.
162 return self._bookings[id]
164 def getSingleBooking(self, type, notify = False):
165 """ Returns the single booking of a plugin who only allows one booking.
166 type: a string with the name of the plugin
167 If the plugin actually allows multiple bookings, an exception will be thrown
168 If the plugin has no booking, None will be returned.
169 Otherwise the booking will be returned
171 if CollaborationTools.getCSBookingClass(type)._allowMultiple:
172 raise CollaborationException("Plugin type " + str(type) + " is not a single-booking plugin")
173 blist = self._bookingsByType.get(type,[])
174 if blist:
175 booking = self._bookings[blist[0]]
176 if notify:
177 try:
178 booking._notifyOnView()
179 except Exception, e:
180 Logger.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e))
181 return booking
182 else:
183 return None
185 def getHiddenBookings(self):
186 if not hasattr(self, '_hiddenBookings'):
187 self._hiddenBookings = set()
188 return self._hiddenBookings
190 def hasBookings(self):
191 return len(self._bookings) > 0
193 def canCreateBooking(self, type):
194 """ Returns if it's possible to create a booking of this given type
196 if not CollaborationTools.getCSBookingClass(type)._allowMultiple:
197 return len(self.getBookingList(filterByType = type)) == 0
198 return True
201 def addBooking(self, booking):
202 """ Adds an existing booking to the list of bookings.
204 booking: The existing booking to be added.
206 self._bookings[booking.getId()] = booking
207 self._bookingsByType.setdefault(booking.getType(),[]).append(booking.getId())
208 if booking.isHidden():
209 self.getHiddenBookings().add(booking.getId())
210 self._indexBooking(booking)
211 self._notifyModification()
213 def createBooking(self, bookingType, bookingParams = {}):
214 """ Adds a new booking to the list of bookings.
215 The id of the new booking is auto-generated incrementally.
216 After generating the booking, its "performBooking" method will be called.
218 bookingType: a String with the booking's plugin. Example: "DummyPlugin", "EVO"
219 bookingParams: a dictionary with the parameters necessary to create the booking.
220 "create the booking" usually means Indico deciding if the booking can take place.
221 if "startDate" and "endDate" are among the keys, they will be taken out of the dictionary.
223 if self.canCreateBooking(bookingType):
224 newBooking = CollaborationTools.getCSBookingClass(bookingType)(bookingType, self._conf)
226 error = newBooking.setBookingParams(bookingParams)
228 if isinstance(error, CSErrorBase):
229 return error
230 elif error:
231 raise CollaborationServiceException("Problem while creating a booking of type " + bookingType)
232 else:
233 newId = self._getNewBookingId()
234 newBooking.setId(newId)
235 createResult = newBooking._create()
236 if isinstance(createResult, CSErrorBase):
237 return createResult
238 else:
239 self._bookings[newId] = newBooking
240 self._bookingsByType.setdefault(bookingType,[]).append(newId)
241 if newBooking.isHidden():
242 self.getHiddenBookings().add(newId)
243 self._indexBooking(newBooking)
244 self._notifyModification()
246 if MailTools.needToSendEmails(bookingType):
247 newBooking._sendNotifications('new')
249 return newBooking
250 else:
251 #we raise an exception because the web interface should take care of this never actually happening
252 raise CollaborationServiceException(bookingType + " only allows to create 1 booking per event")
254 def _indexBooking(self, booking):
255 if booking.shouldBeIndexed():
256 indexes = self._getIndexList(booking)
257 for index in indexes:
258 index.indexBooking(booking)
260 def changeBooking(self, bookingId, bookingParams):
262 Changes the bookingParams of a CSBookingBase object.
263 After updating the booking, its 'performBooking' method will be called.
264 bookingId: the id of the CSBookingBase object to change
265 bookingParams: a dictionary with the new parameters that will modify the booking
266 'modify the booking' can mean that maybe the booking will be rejected with the new parameters.
267 if 'startDate' and 'endDate' are among the keys, they will be taken out of the dictionary.
270 booking = self.getBooking(bookingId)
273 oldStartDate = booking.getStartDate()
274 oldModificationDate = booking.getModificationDate()
275 oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
277 error = booking.setBookingParams(bookingParams)
278 if isinstance(error, CSSanitizationError):
279 return error
280 elif error:
281 CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
282 if isinstance(error, CSErrorBase):
283 return error
284 raise CollaborationServiceException("Problem while modifying a booking of type " + booking.getType())
285 else:
286 modifyResult = booking._modify(oldBookingParams)
287 if isinstance(modifyResult, CSErrorBase):
288 CSBookingManager._rollbackChanges(booking, oldBookingParams, oldModificationDate)
289 return modifyResult
290 else:
291 modificationDate = now_utc()
292 booking.setModificationDate(modificationDate)
294 if booking.isHidden():
295 self.getHiddenBookings().add(booking.getId())
296 elif booking.getId() in self.getHiddenBookings():
297 self.getHiddenBookings().remove(booking.getId())
299 self._changeStartDateInIndex(booking, oldStartDate, booking.getStartDate())
300 self._changeModificationDateInIndex(booking, oldModificationDate, modificationDate)
302 if booking.hasAcceptReject():
303 if booking.getAcceptRejectStatus() is not None:
304 booking.clearAcceptRejectStatus()
305 self._addToPendingIndex(booking)
307 self._notifyModification()
309 if MailTools.needToSendEmails(booking.getType()):
310 booking._sendNotifications('modify')
312 return booking
314 @classmethod
315 def _rollbackChanges(cls, booking, oldBookingParams, oldModificationDate):
316 booking.setBookingParams(oldBookingParams)
317 booking.setModificationDate(oldModificationDate)
319 def _changeConfTitleInIndex(self, booking, oldTitle, newTitle):
320 if booking.shouldBeIndexed():
321 indexes = self._getIndexList(booking)
322 for index in indexes:
323 index.changeEventTitle(booking, oldTitle, newTitle)
325 def _changeStartDateInIndex(self, booking, oldStartDate, newStartDate):
326 if booking.shouldBeIndexed() and booking.hasStartDate():
327 indexes = self._getIndexList(booking)
328 for index in indexes:
329 index.changeStartDate(booking, oldStartDate, newStartDate)
331 def _changeModificationDateInIndex(self, booking, oldModificationDate, newModificationDate):
332 if booking.shouldBeIndexed():
333 indexes = self._getIndexList(booking)
334 for index in indexes:
335 index.changeModificationDate(booking, oldModificationDate, newModificationDate)
337 def _changeConfStartDateInIndex(self, booking, oldConfStartDate, newConfStartDate):
338 if booking.shouldBeIndexed():
339 indexes = self._getIndexList(booking)
340 for index in indexes:
341 index.changeConfStartDate(booking, oldConfStartDate, newConfStartDate)
343 def removeBooking(self, id):
344 """ Removes a booking given its id.
346 booking = self.getBooking(id)
347 bookingType = booking.getType()
349 removeResult = booking._delete()
350 if isinstance(removeResult, CSErrorBase):
351 return removeResult
352 else:
353 del self._bookings[id]
354 self._bookingsByType[bookingType].remove(id)
355 if not self._bookingsByType[bookingType]:
356 del self._bookingsByType[bookingType]
357 if id in self.getHiddenBookings():
358 self.getHiddenBookings().remove(id)
360 self._unindexBooking(booking)
362 self._notifyModification()
364 if MailTools.needToSendEmails(booking.getType()):
365 booking._sendNotifications('remove')
367 return booking
369 def _unindexBooking(self, booking):
370 if booking.shouldBeIndexed():
371 indexes = self._getIndexList(booking)
372 for index in indexes:
373 index.unindexBooking(booking)
375 def startBooking(self, id):
376 booking = self._bookings[id]
377 if booking.canBeStarted():
378 booking._start()
379 return booking
380 else:
381 raise CollaborationException(_("Tried to start booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be started."))
383 def stopBooking(self, id):
384 booking = self._bookings[id]
385 if booking.canBeStopped():
386 booking._stop()
387 return booking
388 else:
389 raise CollaborationException(_("Tried to stop booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be stopped."))
391 def connectBooking(self, id):
392 booking = self._bookings[id]
393 if booking.canBeConnected():
394 return booking._connect()
395 else:
396 raise CollaborationException(_("Tried to connect booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be connected."))
398 def checkBookingStatus(self, id):
399 booking = self._bookings[id]
400 if booking.hasCheckStatus():
401 result = booking._checkStatus()
402 if isinstance(result, CSErrorBase):
403 return result
404 else:
405 return booking
406 else:
407 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."))
409 def acceptBooking(self, id, user = None):
410 booking = self._bookings[id]
411 if booking.hasAcceptReject():
412 if booking.getAcceptRejectStatus() is None:
413 self._removeFromPendingIndex(booking)
414 booking.accept(user)
415 return booking
416 else:
417 raise ServiceError(message=_("Tried to accept booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be accepted."))
419 def rejectBooking(self, id, reason):
420 booking = self._bookings[id]
421 if booking.hasAcceptReject():
422 if booking.getAcceptRejectStatus() is None:
423 self._removeFromPendingIndex(booking)
424 booking.reject(reason)
425 return booking
426 else:
427 raise ServiceError("ERR-COLL10", _("Tried to reject booking ") + str(id) + _(" of meeting ") + str(self._conf.getId()) + _(" but this booking cannot be rejected."))
429 def _addToPendingIndex(self, booking):
430 if booking.shouldBeIndexed():
431 indexes = self._getPendingIndexList(booking)
432 for index in indexes:
433 index.indexBooking(booking)
435 def _removeFromPendingIndex(self, booking):
436 if booking.shouldBeIndexed():
437 indexes = self._getPendingIndexList(booking)
438 for index in indexes:
439 index.unindexBooking(booking)
441 def _getNewBookingId(self):
442 return self._counter.newCount()
444 def _getIndexList(self, booking):
445 """ Returns a list of BookingsIndex objects where the booking should be indexed.
446 This list includes:
447 -an index of all bookings
448 -an index of bookings of the given type
449 -an index of all bookings in the category of the event
450 -an index of booking of the given type, in the category of the event
451 If the booking type declared common indexes:
452 -the common indexes
453 -the common indexes for the category of the event
454 If the booking is of the Accept/Reject type
455 -same indexes as above, but only for pending bookings
457 collaborationIndex = IndexesHolder().getById("collaboration")
458 indexes = [collaborationIndex.getAllBookingsIndex(),
459 collaborationIndex.getIndex(booking.getType())]
461 for commonIndexName in booking.getCommonIndexes():
462 indexes.append(collaborationIndex.getIndex(commonIndexName))
464 if booking.hasAcceptReject() and booking.getAcceptRejectStatus() is None:
465 indexes.extend(self._getPendingIndexList(booking))
467 return indexes
469 def _getPendingIndexList(self, booking):
470 collaborationIndex = IndexesHolder().getById("collaboration")
471 indexes = [collaborationIndex.getIndex("all_pending"),
472 collaborationIndex.getIndex(booking.getType() + "_pending")]
474 for commonIndexName in booking.getCommonIndexes():
475 indexes.append(collaborationIndex.getIndex(commonIndexName + "_pending"))
477 return indexes
479 def getManagers(self):
480 if not hasattr(self, "_managers"):
481 self._managers = {}
482 return self._managers
484 def addPluginManager(self, plugin, user):
485 #TODO: use .linkTo on the user. To be done when the list of roles of a user is actually needed for smth...
486 self.getManagers().setdefault(plugin, []).append(user)
487 self._notifyModification()
489 def removePluginManager(self, plugin, user):
490 #TODO: use .unlinkTo on the user. To be done when the list of roles of a user is actually needed for smth...
491 if user in self.getManagers().setdefault(plugin,[]):
492 self.getManagers()[plugin].remove(user)
493 self._notifyModification()
495 def getVideoServicesManagers(self):
496 return self.getManagers().setdefault('all', [])
498 def isVideoServicesManager(self, user):
499 return user in self.getManagers().setdefault('all', [])
501 def getPluginManagers(self, plugin):
502 return self.getManagers().setdefault(plugin, [])
504 def isPluginManager(self, plugin, user):
505 return user in self.getManagers().setdefault(plugin, [])
507 def getAllManagers(self):
508 """ Returns a list with all the managers, no matter their type
509 The returned list is not ordered.
511 managers = set()
512 for managerList in self.getManagers().itervalues():
513 managers = managers.union(managerList)
514 return list(managers)
516 def isPluginManagerOfAnyPlugin(self, user):
517 #TODO: this method is not optimal. to be optimal, we should store somewhere an index where the key
518 #is the user, and the value is a list of plugins where they are managers.
519 #this could be done with .getLinkTo, but we would need to change the .linkTo method to add extra information
520 #(since we cannot create a role for each plugin)
521 if self.isVideoServicesManager(user):
522 return True
523 else:
524 for plugin in self.getManagers().iterkeys():
525 if self.isPluginManager(plugin, user):
526 return True
527 return False
529 def notifyTitleChange(self, oldTitle, newTitle):
530 """ Notifies the CSBookingManager that the title of the event (meeting) it's attached to has changed.
531 The CSBookingManager will reindex all its bookings in the event title index.
532 This method will be called by the event (meeting) object
534 for booking in self.getBookingList():
535 try:
536 self._changeConfTitleInIndex(booking, oldTitle, newTitle)
537 except Exception, e:
538 Logger.get('VideoServ').exception("Exception while reindexing a booking in the event title index because its event's title changed: " + str(e))
540 def notifyInfoChange(self):
541 self.updateSpeakerWrapperList()
543 def notifyEventDateChanges(self, oldStartDate = None, newStartDate = None, oldEndDate = None, newEndDate = None):
544 """ Notifies the CSBookingManager that the start and / or end dates of the event it's attached to have changed.
545 The CSBookingManager will change the dates of all the bookings that want to be updated.
546 If there are problems (such as a booking not being able to be modified)
547 it will write a list of strings describing the problems in the 'dateChangeNotificationProblems' context variable.
548 (each string is produced by the _booking2NotifyProblem method).
549 This method will be called by the event (meeting) object.
551 startDateChanged = oldStartDate is not None and newStartDate is not None and not oldStartDate == newStartDate
552 endDateChanged = oldEndDate is not None and newEndDate is not None and not oldEndDate == newEndDate
553 someDateChanged = startDateChanged or endDateChanged
555 Logger.get("VideoServ").info("""CSBookingManager: starting notifyEventDateChanges. Arguments: confId=%s, oldStartDate=%s, newStartDate=%s, oldEndDate=%s, newEndDate=%s""" %
556 (str(self._conf.getId()), str(oldStartDate), str(newStartDate), str(oldEndDate), str(newEndDate)))
558 if someDateChanged:
559 problems = []
560 for booking in self.getBookingList():
561 if booking.hasStartDate():
562 if startDateChanged:
563 try:
564 self._changeConfStartDateInIndex(booking, oldStartDate, newStartDate)
565 except Exception, e:
566 Logger.get('VideoServ').error("Exception while reindexing a booking in the event start date index because its event's start date changed: " + str(e))
568 if booking.needsToBeNotifiedOfDateChanges():
569 Logger.get("VideoServ").info("""CSBookingManager: notifying date changes to booking %s of event %s""" %
570 (str(booking.getId()), str(self._conf.getId())))
571 oldBookingStartDate = booking.getStartDate()
572 oldBookingEndDate = booking.getEndDate()
573 oldBookingParams = booking.getBookingParams() #this is a copy so it's ok
575 if startDateChanged:
576 booking.setStartDate(oldBookingStartDate + (newStartDate - oldStartDate) )
577 if endDateChanged:
578 booking.setEndDate(oldBookingEndDate + (newEndDate - oldEndDate) )
580 rollback = False
581 modifyResult = None
582 try:
583 modifyResult = booking._modify(oldBookingParams)
584 if isinstance(modifyResult, CSErrorBase):
585 Logger.get('VideoServ').warning("""Error while changing the dates of booking %s of event %s after event dates changed: %s""" %
586 (str(booking.getId()), str(self._conf.getId()), modifyResult.getLogMessage()))
587 rollback = True
588 except Exception, e:
589 Logger.get('VideoServ').error("""Exception while changing the dates of booking %s of event %s after event dates changed: %s""" %
590 (str(booking.getId()), str(self._conf.getId()), str(e)))
591 rollback = True
593 if rollback:
594 booking.setStartDate(oldBookingStartDate)
595 booking.setEndDate(oldBookingEndDate)
596 problems.append(CSBookingManager._booking2NotifyProblem(booking, modifyResult))
597 elif startDateChanged:
598 self._changeStartDateInIndex(booking, oldBookingStartDate, booking.getStartDate())
600 if hasattr(booking, "notifyEventDateChanges"):
601 try:
602 booking.notifyEventDateChanges(oldStartDate, newStartDate, oldEndDate, newEndDate)
603 except Exception, e:
604 Logger.get('VideoServ').exception("Exception while notifying a plugin of an event date changed: " + str(e))
606 if problems:
607 ContextManager.get('dateChangeNotificationProblems')['Collaboration'] = [
608 'Some Video Services bookings could not be moved:',
609 problems,
610 'Go to [[' + str(urlHandlers.UHConfModifCollaboration.getURL(self.getOwner(), secure = ContextManager.get('currentRH').use_https())) + ' the Video Services section]] to modify them yourself.'
614 def notifyTimezoneChange(self, oldTimezone, newTimezone):
615 """ Notifies the CSBookingManager that the timezone of the event it's attached to has changed.
616 The CSBookingManager will change the dates of all the bookings that want to be updated.
617 This method will be called by the event (Conference) object
619 return []
621 def notifyLocationChange(self):
622 for booking in self.getBookingList():
623 if hasattr(booking, "notifyLocationChange"):
624 try:
625 booking.notifyLocationChange()
626 except Exception, e:
627 Logger.get('VideoServ').exception("Exception while notifying a plugin of a location change: " + str(e))
629 @classmethod
630 def _booking2NotifyProblem(cls, booking, modifyError):
631 """ Turns a booking into a string used to tell the user
632 why a date change of a booking triggered by the event's start or end date change
633 went bad.
636 message = []
637 message.extend(["The dates of the ", booking.getType(), " booking"])
638 if booking.hasTitle():
639 message.extend([': "', booking._getTitle(), '" (', booking.getStartDateAsString(), ' - ', booking.getEndDateAsString(), ')'])
640 else:
641 message.extend([' ongoing from ', booking.getStartDateAsString(), ' to ', booking.getEndDateAsString(), ''])
643 message.append(' could not be changed.')
644 if modifyError and modifyError.getUserMessage():
645 message.extend([' Reason: ', modifyError.getUserMessage()])
646 return "".join(message)
649 def notifyDeletion(self):
650 """ Notifies the CSBookingManager that the Conference object it is attached to has been deleted.
651 The CSBookingManager will change the dates of all the bookings that want to be updated.
652 This method will be called by the event (Conference) object
654 for booking in self.getBookingList():
655 try:
656 removeResult = booking._delete()
657 if isinstance(removeResult, CSErrorBase):
658 Logger.get('VideoServ').warning("Error while deleting a booking of type %s after deleting an event: %s"%(booking.getType(), removeResult.getLogMessage() ))
659 self._unindexBooking(booking)
660 except Exception, e:
661 Logger.get('VideoServ').exception("Exception while deleting a booking of type %s after deleting an event: %s" % (booking.getType(), str(e)))
663 def getEventDisplayPlugins(self, sorted = False):
664 """ Returns a list of names (strings) of plugins which have been configured
665 as showing bookings in the event display page, and which have bookings
666 already (or previously) created in the event.
667 (does not check if the bookings are hidden or not)
670 pluginsWithEventDisplay = CollaborationTools.pluginsWithEventDisplay()
671 l = []
672 for pluginName in self._bookingsByType:
673 if pluginName in pluginsWithEventDisplay:
674 l.append(pluginName)
675 if sorted:
676 l.sort()
677 return l
679 def createTestBooking(self, bookingParams = {}):
680 """ Function that creates a 'test' booking for performance test.
681 Avoids to use any of the plugins except DummyPlugin
683 from MaKaC.plugins.Collaboration.DummyPlugin.collaboration import CSBooking as DummyBooking
684 bookingType = 'DummyPlugin'
685 newBooking = DummyBooking(bookingType, self._conf)
686 error = newBooking.setBookingParams(bookingParams)
687 if error:
688 raise CollaborationServiceException("Problem while creating a test booking")
689 else:
690 newId = self._getNewBookingId()
691 newBooking.setId(newId)
692 createResult = newBooking._create()
693 if isinstance(createResult, CSErrorBase):
694 return createResult
695 else:
696 self._bookings[newId] = newBooking
697 self._bookingsByType.setdefault(bookingType,[]).append(newId)
698 if newBooking.isHidden():
699 self.getHiddenBookings().add(newId)
700 self._indexBooking(newBooking)
701 self._notifyModification()
702 return newBooking
704 def _notifyModification(self):
705 self._p_changed = 1
707 def getSortedContributionSpeaker(self, exclusive):
708 ''' This method will create a dictionary by sorting the contribution/speakers
709 that they are in recording, webcast or in both.
710 bool: exclusive - if True, every dicts (recording, webcast, both) will
711 have different speaker list (no repetition allowed)
712 if an element is present in 'both', it will be deleted from
713 'recording and 'webcast'
715 returns d = { 'recording': {}, 'webcast' : {}, 'both': {} }
718 d = {}
720 recordingBooking = self.getSingleBooking("RecordingRequest")
721 webcastBooking = self.getSingleBooking("WebcastRequest")
723 d["recording"] = recordingBooking.getContributionSpeakerSingleBooking() if recordingBooking else {}
724 d["webcast"] = webcastBooking.getContributionSpeakerSingleBooking() if webcastBooking else {}
726 contributions = {}
727 ''' Look for speaker intersections between 'recording' and 'webcast' dicts
728 and put them in 'both' dict. Additionally, if any intersection has been found,
729 we exclude them from the original dictionary.
731 for cont in d["recording"].copy():
732 if cont in d["webcast"].copy():
733 # Check if same contribution/speaker in 'recording' and 'webcast'
734 intersection = set(d['recording'][cont]) & set(d['webcast'][cont])
735 if intersection:
736 contributions[cont] = list(intersection)
738 # if exclusive is True, and as we found same contribution/speaker,
739 # we delete them from 'recording' and 'webcast' dicts
740 if exclusive:
741 exclusion = set(d['recording'][cont]) ^ set(contributions[cont])
742 if not exclusion:
743 del d["recording"][cont]
744 else:
745 d["recording"][cont] = list(exclusion)
747 exclusion = set(d['webcast'][cont]) ^ set(contributions[cont])
748 if not exclusion:
749 del d["webcast"][cont]
750 else:
751 d["webcast"][cont] = list(exclusion)
753 d["both"] = contributions
755 return d
757 def getContributionSpeakerByType(self, requestType):
758 ''' Return a plain dict of contribution/speaker according to the requestType
759 if the request type is 'both', we need to merge the lists
761 d = self.getSortedContributionSpeaker(False) # We want non exclusive dict
763 if requestType == "recording":
764 return d['recording']
765 elif requestType == "webcast":
766 return d['webcast']
767 elif requestType == "both":
768 # We merge 'recording' and 'webcast'
769 m = dict(((cont, list(set(spks) | \
770 set(d['webcast'].get(cont, [])))) for cont, spks in d['recording'].iteritems()))
771 m.update(dict((cont, spks) for cont, spks in d['webcast'].iteritems() if cont not in m))
773 return m
774 else:
775 return {}
777 def updateSpeakerWrapperList(self, newList = False):
779 if newList arg is True, don't check if there is an existing speakerWrapperList
780 and create a new one straight forward. (Done to avoid loops)
782 SWList = []
783 contributions = self.getSortedContributionSpeaker(True)
784 requestType = ['recording', 'webcast', 'both']
786 for type in requestType:
787 for cont in contributions[type]:
788 for spk in contributions[type][cont]:
789 if newList:
790 sw = None
791 else:
792 sw = self.getSpeakerWrapperByUniqueId("%s.%s"%(cont, spk.getId()))
794 if sw:
795 if not sw.getObject().getEmail():
796 if sw.getStatus() not in [SpeakerStatusEnum.SIGNED,
797 SpeakerStatusEnum.FROMFILE,
798 SpeakerStatusEnum.REFUSED]:
799 sw.setStatus(SpeakerStatusEnum.NOEMAIL)
800 elif sw.getStatus() == SpeakerStatusEnum.NOEMAIL:
801 sw.setStatus(SpeakerStatusEnum.NOTSIGNED)
802 sw.setRequestType(type)
803 SWList.append(sw)
804 else:
805 newSw = SpeakerWrapper(spk, cont, type)
806 if not newSw.getObject().getEmail():
807 newSw.setStatus(SpeakerStatusEnum.NOEMAIL)
808 SWList.append(newSw)
810 self._speakerWrapperList = SWList
812 def getSpeakerWrapperList(self):
813 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
814 self.updateSpeakerWrapperList(True)
816 return self._speakerWrapperList
818 def getSpeakerWrapperByUniqueId(self, id):
820 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
821 self.updateSpeakerWrapperList(True)
823 for spkWrap in self._speakerWrapperList:
824 if spkWrap.getUniqueId() == id:
825 return spkWrap
827 return None
829 def areSignatureCompleted(self):
830 value = True;
831 for spkWrap in self._speakerWrapperList:
832 if spkWrap.getStatus() != SpeakerStatusEnum.FROMFILE and \
833 spkWrap.getStatus() != SpeakerStatusEnum.SIGNED:
834 value = False;
836 return value
838 def getSpeakerWrapperListByStatus(self, status):
839 '''Return a list of SpeakerWrapper matching the status.
841 list = []
842 for spkWrap in self._speakerWrapperList:
843 if spkWrap.getStatus() == status:
844 list.append(spkWrap)
846 return list
848 def getSpeakerEmailByUniqueId(self, id, user):
849 ''' Return the email of a speaker according to the uniqueId.
850 id: uniqueId of the speaker wrapper.
851 user: user object of the sender of the emails, in order to check the rights.
854 canManageRequest = CollaborationTools.getRequestTypeUserCanManage(self._conf, user)
855 requestTypeAccepted = ""
857 if canManageRequest == "recording":
858 requestTypeAccepted = ["recording"]
859 elif canManageRequest == "webcast":
860 requestTypeAccepted = ["webcast"]
861 elif canManageRequest == "both":
862 requestTypeAccepted = ["recording", "webcast", "both"]
864 list = []
865 for spkWrap in self._speakerWrapperList:
866 if spkWrap.getUniqueId() == id and \
867 spkWrap.hasEmail() and spkWrap.getStatus() not in \
868 [SpeakerStatusEnum.SIGNED, SpeakerStatusEnum.FROMFILE] and \
869 spkWrap.getRequestType() in requestTypeAccepted:
871 list.append(spkWrap.getObject().getEmail())
873 return list
875 def isAnyRequestAccepted(self):
877 Return true if at least one between recording and webcast request
878 has been accepted.
880 value = False
881 rr = self.getSingleBooking("RecordingRequest")
882 wr = self.getSingleBooking("WebcastRequest")
884 if rr:
885 value = rr.getAcceptRejectStatus()
887 if wr:
888 value = value or wr.getAcceptRejectStatus()
890 return value
892 def isContributionReadyToBePublished(self, contId):
893 if not hasattr(self, "_speakerWrapperList"):#TODO: remove when safe
894 self.updateSpeakerWrapperList(True)
896 exists = False
897 for spkWrap in self._speakerWrapperList:
898 if spkWrap.getContId() == contId:
899 exists = True
900 if spkWrap.getStatus() != SpeakerStatusEnum.SIGNED and \
901 spkWrap.getStatus() != SpeakerStatusEnum.FROMFILE:
902 return False
904 #The list has to have at least one spkWrap with the given contId
905 return exists
907 class CSBookingBase(Persistent, Fossilizable):
908 fossilizes(ICSBookingBaseConfModifFossil, ICSBookingBaseIndexingFossil)
910 """ Base class that represents a Collaboration Systems booking.
911 Every Collaboration plugin will have to implement this class.
912 In the base class are gathered all the functionalities / elements that are common for all plugins.
913 A booking is Persistent (DateChangeObserver inherits from Persistent) so it will be stored in the database.
914 Also, every CSBookingBase object in the server will be mirrored by a Javascript object in the client, through "Pickling".
916 Every class that implements the CSBookingBase has to declare the following class attributes:
917 _hasStart : True if the plugin has a "start" concept. Otherwise, the "start" button will not appear, etc.
918 _hasStop : True if the plugin has a "stop" concept. Otherwise, the "stop" button will not appear, etc.
919 _hasConnect : True if the plugin has a "connect" concept. Otherwise, the "connect" button will not appear, etc.
920 _hasCheckStatus: True if the plugin has a "check status" concept. Otherwise, the "check status" button will not appear, etc.
921 _hasAcceptReject: True if the plugin has a "accept or reject" concept. Otherwise, the "accept" and "reject" buttons will not appear, etc.
922 _requiresServerCallForStart : True if we should notify the server when the user presses the "start" button.
923 _requiresServerCallForStop : True if we should notify the server when the user presses the "stop" button.
924 _requiresServerCallForConnect : True if we should notify the server when the user presses the "connect" button.
925 _requiresClientCallForStart : True if the browser should execute some JS action when the user presses the "start" button.
926 _requiresClientCallForStop : True if the browser should execute some JS action when the user presses the "stop" button.
927 _requiresClientCallForConnect : True if the browser should execute some JS action when the user presses the "connect" button.
928 _needsBookingParamsCheck : True if the booking parameters should be checked after the booking is added / edited.
929 If True, the _checkBookingParams method will be called by the setBookingParams method.
930 _needsToBeNotifiedOnView: True if the booking object needs to be notified (through the "notifyOnView" method)
931 when the user "sees" the booking, for example when returning the list of bookings.
932 _canBeNotifiedOfEventDateChanges: True if bookings of this type should be able to be notified
933 of their owner Event changing start date, end date or timezone.
934 _allowMultiple: True if this booking type allows more than 1 booking per event.
937 _hasStart = False
938 _hasStop = False
939 _hasConnect = False
940 _hasCheckStatus = False
941 _hasAcceptReject = False
942 _hasStartStopAll = False
943 _requiresServerCallForStart = False
944 _requiresServerCallForStop = False
945 _requiresServerCallForConnect = False
946 _requiresClientCallForStart = False
947 _requiresClientCallForStop = False
948 _requiresClientCallForConnect = False
949 _needsBookingParamsCheck = False
950 _needsToBeNotifiedOnView = False
951 _canBeNotifiedOfEventDateChanges = True
952 _allowMultiple = True
953 _shouldBeIndexed = True
954 _commonIndexes = []
955 _hasStartDate = True
956 _hasEventDisplay = False
957 _hasTitle = False
958 _adminOnly = False
959 _complexParameters = []
961 def __init__(self, bookingType, conf):
962 """ Constructor for the CSBookingBase class.
963 id: a string with the id of the booking
964 bookingType: a string with the type of the booking. Example: "DummyPlugin", "EVO"
965 conf: a Conference object to which this booking belongs (through the CSBookingManager object). The meeting of this booking.
966 startTime: TODO
967 endTime: TODO
969 Other attributes initialized by this constructor:
970 -_bookingParams: the parameters necessary to perform the booking.
971 The plugins will decide if the booking gets authorized or not depending on this.
972 Needs to be defined by the implementing class, as keys with empty values.
973 -_startingParams: the parameters necessary to start the booking.
974 They will be used on the client for the local start action.
975 Needs to be defined by the implementing class, as keys with empty values.
976 -_warning: A warning is a plugin-defined object, with information to show to the user when
977 the operation went well but we still have to show some info to the user.
978 -_permissionToStart : Even if the "start" button for a booking is able to be pushed, there may be cases where the booking should
979 not start. For example, if it's not the correct time yet.
980 In that case "permissionToStart" should be set to false so that the booking doesn't start.
981 -_permissionToStop: Same as permissionToStart. Sometimes the booking should not be allowed to stop even if the "stop" button is available.
982 -_permissionToConnect: Same as permissionToStart. Sometimes the booking should not be allowed to connect even if the "connect" button is available.
984 self._id = None
985 self._type = bookingType
986 self._plugin = CollaborationTools.getPlugin(self._type)
987 self._conf = conf
988 self._warning = None
989 self._creationDate = nowutc()
990 self._modificationDate = nowutc()
991 self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
992 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
993 self._startDate = None
994 self._endDate = None
995 self._startDateTimestamp = None
996 self._endDateTimestamp = None
997 self._acceptRejectStatus = None #None = not yet accepted / rejected; True = accepted; False = rejected
998 self._rejectReason = ""
999 self._bookingParams = {}
1000 self._canBeDeleted = True
1001 self._permissionToStart = False
1002 self._permissionToStop = False
1003 self._permissionToConnect = False
1004 self._needsToBeNotifiedOfDateChanges = self._canBeNotifiedOfEventDateChanges
1005 self._hidden = False
1006 self._play_status = None
1008 setattr(self, "_" + bookingType + "Options", CollaborationTools.getPlugin(bookingType).getOptions())
1009 #NOTE: Should maybe notify the creation of a new booking, specially if it's a single booking
1010 # like that can update requestType of the speaker wrapper...
1012 def getId(self):
1013 """ Returns the internal, per-conference id of the booking.
1014 This attribute will be available in Javascript with the "id" identifier.
1016 return self._id
1018 def setId(self, id):
1019 """ Sets the internal, per-conference id of the booking
1021 self._id = id
1023 def getUniqueId(self):
1024 """ Returns an unique Id that identifies this booking server-wide.
1025 Useful for ExternalOperationsManager
1027 return "%scsbook%s" % (self.getConference().getUniqueId(), self.getId())
1029 def getType(self):
1030 """ Returns the type of the booking, as a string: "EVO", "DummyPlugin"
1031 This attribute will be available in Javascript with the "type" identifier.
1033 return self._type
1035 def getConference(self):
1036 """ Returns the owner of this CSBookingBase object, which is a Conference object representing the meeting.
1038 return self._conf
1040 def setConference(self, conf):
1041 """ Sets the owner of this CSBookingBase object, which is a Conference object representing the meeting.
1043 self._conf = conf
1045 def getWarning(self):
1046 """ Returns a warning attached to this booking.
1047 A warning is a plugin-defined object, with information to show to the user when
1048 the operation went well but we still have to show some info to the user.
1049 To be overloaded by plugins.
1051 if not hasattr(self, '_warning'):
1052 self._warning = None
1053 return self._warning
1055 def setWarning(self, warning):
1056 """ Sets a warning attached to this booking.
1057 A warning is a plugin-defined object, with information to show to the user when
1058 the operation went well but we still have to show some info to the user.
1059 To be overloaded by plugins.
1061 self._warning = warning
1063 def getCreationDate(self):
1064 """ Returns the date this booking was created, as a timezone localized datetime object
1066 if not hasattr(self, "_creationDate"): #TODO: remove when safe
1067 self._creationDate = nowutc()
1068 return self._creationDate
1070 def getAdjustedCreationDate(self, tz=None):
1071 """ Returns the booking creation date, adjusted to a given timezone.
1072 If no timezone is provided, the event's timezone is used
1074 return getAdjustedDate(self.getCreationDate(), self.getConference(), tz)
1076 def getCreationDateTimestamp(self):
1077 if not hasattr(object, "_creationDateTimestamp"): #TODO: remove when safe
1078 self._creationDateTimestamp = int(datetimeToUnixTimeInt(self._creationDate))
1079 return self._creationDateTimestamp
1081 def getModificationDate(self):
1082 """ Returns the date this booking was modified last
1084 if not hasattr(self, "_modificationDate"): #TODO: remove when safe
1085 self._modificationDate = nowutc()
1086 return self._modificationDate
1088 def getAdjustedModificationDate(self, tz=None):
1089 """ Returns the booking last modification date, adjusted to a given timezone.
1090 If no timezone is provided, the event's timezone is used
1092 return getAdjustedDate(self.getModificationDate(), self.getConference(), tz)
1094 def getModificationDateTimestamp(self):
1095 if not hasattr(object, "_modificationDateTimestamp"): #TODO: remove when safe
1096 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(self._modificationDate))
1097 return self._modificationDateTimestamp
1099 def setModificationDate(self, date):
1100 """ Sets the date this booking was modified last
1102 self._modificationDate = date
1103 if date:
1104 self._modificationDateTimestamp = int(datetimeToUnixTimeInt(date))
1105 else:
1106 self._modificationDateTimestamp = None
1108 def getBookingsOfSameType(self, sorted = False):
1109 """ Returns a list of the bookings of the same type as this one (including this one)
1110 sorted: if true, bookings will be sorted by id
1112 return self._conf.getCSBookingManager().getBookingList(sorted, self._type)
1114 def getPlugin(self):
1115 """ Returns the Plugin object associated to this booking.
1117 return self._plugin
1119 def setPlugin(self, plugin):
1120 """ Sets the Plugin object associated to this booking.
1122 self._plugin = plugin
1124 def getPluginOptions(self):
1125 """ Utility method that returns the plugin options for this booking's type of plugin
1127 return self._plugin.getOptions()
1129 def getPluginOptionByName(self, optionName):
1130 """ Utility method that returns a plugin option, given its name, for this booking's type of plugin
1132 return self.getPluginOptions()[optionName]
1134 def getStartDate(self):
1135 """ Returns the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1137 return self._startDate
1139 def getAdjustedStartDate(self, tz=None):
1140 """ Returns the booking start date, adjusted to a given timezone.
1141 If no timezone is provided, the event's timezone is used
1143 if self.getStartDate():
1144 return getAdjustedDate(self.getStartDate(), self.getConference(), tz)
1145 else:
1146 return None
1148 def getStartDateTimestamp(self):
1149 if not hasattr(object, "_startDateTimestamp"): #TODO: remove when safe
1150 self._startDateTimestamp = int(datetimeToUnixTimeInt(self._startDate))
1151 return self._startDateTimestamp
1153 def setStartDateTimestamp(self, startDateTimestamp):
1154 self._startDateTimestamp = startDateTimestamp
1156 def getStartDateAsString(self):
1157 """ Returns the start date as a string, expressed in the meeting's timezone
1159 if self._startDate == None:
1160 return ""
1161 else:
1162 return formatDateTime(self.getAdjustedStartDate(), locale='en_US')
1164 def setStartDate(self, startDate):
1165 """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1167 self._startDate = startDate
1168 if startDate:
1169 self._startDateTimestamp = int(datetimeToUnixTimeInt(startDate))
1170 else:
1171 self._startDateTimestamp = None
1173 def setStartDateFromString(self, startDateString):
1174 """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
1176 if startDateString == "":
1177 self.setStartDate(None)
1178 else:
1179 try:
1180 self.setStartDate(setAdjustedDate(parseDateTime(startDateString), self._conf))
1181 except ValueError:
1182 raise CollaborationServiceException("startDate parameter (" + startDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
1184 def getEndDate(self):
1185 """ Returns the end date as an datetime object with timezone information (adjusted to the meeting's timezone)
1187 return self._endDate
1189 def isHappeningNow(self):
1190 now = nowutc()
1191 return self.getStartDate() < now and self.getEndDate() > now
1193 def hasHappened(self):
1194 now = nowutc()
1195 return now > self.getEndDate()
1197 def getAdjustedEndDate(self, tz=None):
1198 """ Returns the booking end date, adjusted to a given timezone.
1199 If no timezone is provided, the event's timezone is used
1201 return getAdjustedDate(self.getEndDate(), self.getConference(), tz)
1203 def getEndDateTimestamp(self):
1204 if not hasattr(object, "_endDateTimestamp"): #TODO: remove when safe
1205 self._endDateTimestamp = int(datetimeToUnixTimeInt(self._endDate))
1206 return self._endDateTimestamp
1208 def setEndDateTimestamp(self, endDateTimestamp):
1209 self._endDateTimestamp = endDateTimestamp
1211 def getEndDateAsString(self):
1212 """ Returns the start date as a string, expressed in the meeting's timezone
1214 if self._endDate == None:
1215 return ""
1216 else:
1217 return formatDateTime(self.getAdjustedEndDate(), locale='en_US')
1219 def setEndDate(self, endDate):
1220 """ Sets the start date as an datetime object with timezone information (adjusted to the meeting's timezone)
1222 self._endDate = endDate
1223 if endDate:
1224 self._endDateTimestamp = int(datetimeToUnixTimeInt(endDate))
1225 else:
1226 self._endDateTimestamp = None
1228 def setEndDateFromString(self, endDateString):
1229 """ Sets the start date from a string. It is assumed that the date is expressed in the meeting's timezone
1231 if endDateString == "":
1232 self.setEndDate(None)
1233 else:
1234 try:
1235 self.setEndDate(setAdjustedDate(parseDateTime(endDateString), self._conf))
1236 except ValueError:
1237 raise CollaborationServiceException("endDate parameter (" + endDateString +" ) is in an incorrect format for booking with id: " + str(self._id))
1239 def getStatusMessage(self):
1240 """ Returns the status message as a string.
1241 This attribute will be available in Javascript with the "statusMessage"
1243 status = self.getPlayStatus()
1244 if status == None:
1245 if self.isHappeningNow():
1246 return _("Ready to start!")
1247 elif self.hasHappened():
1248 return _("Already took place")
1249 else:
1250 return _("Booking created")
1251 elif status:
1252 return _("Conference started")
1253 elif not status:
1254 return _("Conference stopped")
1256 def getStatusClass(self):
1257 """ Returns the status message CSS class as a string.
1258 This attribute will be available in Javascript with the "statusClass"
1260 if self.getPlayStatus() == None or self.hasHappened():
1261 return "statusMessageOther"
1262 else:
1263 return "statusMessageOK"
1265 def accept(self, user = None):
1266 """ Sets this booking as accepted
1268 self._acceptRejectStatus = True
1269 self._accept(user)
1271 def reject(self, reason):
1272 """ Sets this booking as rejected, and stores the reason
1274 self._acceptRejectStatus = False
1275 self._rejectReason = reason
1276 self._reject()
1278 def clearAcceptRejectStatus(self):
1279 """ Sets back the accept / reject status to None
1281 self._acceptRejectStatus = None
1283 def getAcceptRejectStatus(self):
1284 """ Returns the Accept/Reject status of the booking
1285 This attribute will be available in Javascript with the "acceptRejectStatus"
1286 Its value will be:
1287 -None if the booking has not been accepted or rejected yet,
1288 -True if it has been accepted,
1289 -False if it has been rejected
1291 if not hasattr(self, "_acceptRejectStatus"):
1292 self._acceptRejectStatus = None
1293 return self._acceptRejectStatus
1295 def getRejectReason(self):
1296 """ Returns the rejection reason.
1297 This attribute will be available in Javascript with the "rejectReason"
1299 if not hasattr(self, "_rejectReason"):
1300 self._rejectReason = ""
1301 return self._rejectReason
1303 def getBookingParams(self):
1304 """ Returns a dictionary with the booking params.
1305 This attribute will be available in Javascript with the "bookingParams"
1307 If self._bookingParams has not been set by the implementing class, an exception is thrown.
1309 Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1310 be retrieved through getter methods.
1311 If a subclass defines a class attributes called _complexParameters (a list of strings),
1312 parameter names that are in this list will also be included in the returned dictionary.
1313 Their value will be retrieved by calling the corresponding getXXX methods
1314 but instead the inheriting class's setXXX method will be called.
1315 Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] correspond
1316 to the methods getCommunityName, getAccessPassword, getHasAccessPassword.
1317 If you include a parameter in the _complexParameters list, you always have to implement the corresponding getter method.
1319 bookingParams = {}
1320 for k, v in self.__class__._simpleParameters.iteritems():
1321 if k in self._bookingParams:
1322 value = self._bookingParams[k]
1323 else:
1324 value = v[1] #we use the default value
1325 if v[0] is bool and value is True: #we assume it will be used in a single checkbox
1326 value = ["yes"]
1327 if value is not False: #we do not include False, it means the single checkbox is not checked
1328 bookingParams[k] = value
1330 if hasattr(self.__class__, "_complexParameters") and len(self.__class__._complexParameters) > 0:
1331 getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1332 for paramName in self.__class__._complexParameters:
1333 getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1334 if getMethodName in getterMethods:
1335 bookingParams[paramName] = getterMethods[getMethodName]()
1336 else:
1337 raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1339 bookingParams["startDate"] = self.getStartDateAsString()
1340 bookingParams["endDate"] = self.getEndDateAsString()
1341 if self.needsToBeNotifiedOfDateChanges():
1342 bookingParams["notifyOnDateChanges"] = ["yes"]
1343 if self.isHidden():
1344 bookingParams["hidden"] = ["yes"]
1345 return bookingParams
1348 def getBookingParamByName(self, paramName):
1349 if paramName in self.__class__._simpleParameters:
1350 if not paramName in self._bookingParams:
1351 self._bookingParams[paramName] = self.__class__._simpleParameters[paramName][1]
1352 return self._bookingParams[paramName]
1353 elif hasattr(self.__class__, "_complexParameters") and paramName in self.__class__._complexParameters:
1354 getterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('get')))
1355 getMethodName = 'get' + paramName[0].upper() + paramName[1:]
1356 if getMethodName in getterMethods:
1357 return getterMethods[getMethodName]()
1358 else:
1359 raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName) + " but the corresponding getter method " + getMethodName + " is not implemented")
1360 else:
1361 raise CollaborationServiceException("Tried to retrieve parameter " + str(paramName) + " but this parameter does not exist")
1363 def getContributionSpeakerSingleBooking(self):
1364 ''' Return a dictionnary with the contributions and their speakers that need to be recorded
1365 e.g: {contId:[Spk1Object, Spk2Object, Spk3Object], cont2:[Spk1Object]}...
1367 request = {}
1369 recordingTalksChoice = self.getBookingParams()["talks"] #either "all", "choose" or ""
1370 listTalksToRecord = self.getBookingParams()["talkSelection"]
1372 if self._conf.getType() == "simple_event":
1373 request[self._conf.getId()] = []
1374 for chair in self._conf.getChairList():
1375 request[self._conf.getId()].append(chair)
1376 else:
1377 for cont in self._conf.getContributionList():
1378 ''' We select the contributions that respect the following conditions:
1379 - They have Speakers assigned.
1380 - They are scheduled. (to discuss...)
1381 - They have been chosen for the recording request.
1383 if recordingTalksChoice != "choose" or cont.getId() in listTalksToRecord:
1384 if cont.isScheduled():
1385 request[cont.getId()] = []
1386 for spk in cont.getSpeakerList():
1387 request[cont.getId()].append(spk)
1389 return request
1391 def setBookingParams(self, params):
1392 """ Sets new booking parameters.
1393 params: a dict with key/value pairs with the new values for the booking parameters.
1394 If the plugin's _needsBookingParamsCheck is True, the _checkBookingParams() method will be called.
1395 This function will return False if all the checks were OK or if there were no checks, and otherwise will throw
1396 an exception or return a CSReturnedErrorBase error.
1398 Support for "complex" parameters, that are not defined in the self._bookingParams dict, but can
1399 be set through setter methods.
1400 If a subclass defines a class attributes called _complexParameters (a list of strings),
1401 parameter names that are in 'params' and also in this list will not be assigned directly,
1402 but instead the inheriting class's setXXX method will be called.
1404 Example: _complexParameters = ["communityName", "accessPassword", "hasAccessPassword"] corresponds
1405 to methods setCommunityName, setAccessPassword, setHasAccessPassword.
1406 Note: even if a parameter is in this list, you can decide not to implement its corresponding set
1407 method if you never expect the parameter name to come up inside 'params'.
1410 sanitizeResult = self.sanitizeParams(params)
1411 if sanitizeResult:
1412 return sanitizeResult
1414 self.setHidden(params.pop("hidden", False) == ["yes"])
1415 self.setNeedsToBeNotifiedOfDateChanges(params.pop("notifyOnDateChanges", False) == ["yes"])
1417 startDate = params.pop("startDate", None)
1418 if startDate is not None:
1419 self.setStartDateFromString(startDate)
1420 endDate = params.pop("endDate", None)
1421 if endDate is not None:
1422 self.setEndDateFromString(endDate)
1424 for k,v in params.iteritems():
1425 if k in self.__class__._simpleParameters:
1426 if self.__class__._simpleParameters[k][0]:
1427 try:
1428 v = self.__class__._simpleParameters[k][0](v)
1429 except ValueError:
1430 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")
1431 self._bookingParams[k] = v
1432 elif k in self.__class__._complexParameters:
1433 setterMethods = dict(inspect.getmembers(self, lambda m: inspect.ismethod(m) and m.__name__.startswith('set')))
1434 setMethodName = 'set' + k[0].upper() + k[1:]
1435 if setMethodName in setterMethods:
1436 setterMethods[setMethodName](v)
1437 else:
1438 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")
1439 else:
1440 raise CollaborationServiceException("Tried to set the value of a parameter with name " + str(k) + " that was not declared")
1442 for k, v in self.__class__._simpleParameters.iteritems():
1443 if not k in self._bookingParams:
1444 self._bookingParams[k] = self.__class__._simpleParameters[k][1]
1446 if self.needsBookingParamsCheck():
1447 return self._checkBookingParams()
1449 return False
1451 def sanitizeParams(self, params):
1452 """ Checks if the fields introduced into the booking / request form
1453 have any kind of HTML or script tag.
1455 if not isinstance(params, dict):
1456 raise CollaborationServiceException("Booking parameters are not a dictionary")
1458 invalidFields = []
1459 for k, v in params.iteritems():
1460 if type(v) == str and hasTags(v):
1461 invalidFields.append(k)
1463 if invalidFields:
1464 return CSSanitizationError(invalidFields)
1465 else:
1466 return None
1468 def _getTypeDisplayName(self):
1469 return CollaborationTools.getXMLGenerator(self._type).getDisplayName()
1471 def _getFirstLineInfo(self, tz):
1472 return CollaborationTools.getXMLGenerator(self._type).getFirstLineInfo(self, tz)
1474 def _getTitle(self):
1475 if self.hasEventDisplay():
1476 raise CollaborationException("Method _getTitle was not overriden for the plugin type " + str(self._type))
1478 def _getInformationDisplay(self, tz):
1479 templateClass = CollaborationTools.getTemplateClass(self.getType(), "WInformationDisplay")
1480 if templateClass:
1481 return templateClass(self, tz).getHTML()
1482 else:
1483 return None
1485 def _getLaunchDisplayInfo(self):
1486 """ To be overloaded by plugins
1488 return None
1490 def _checkBookingParams(self):
1491 """ To be overriden by inheriting classes.
1492 Verifies that the booking parameters are correct. For example, that a numeric field is actually a number.
1493 Otherwise, an exception should be thrown.
1494 If there are no errors, the method should just return.
1496 if self.needsBookingParamsCheck():
1497 raise CollaborationServiceException("Method _checkBookingParams was not overriden for the plugin type " + str(self._type))
1499 def hasStart(self):
1500 """ Returns if this booking belongs to a plugin who has a "start" concept.
1501 This attribute will be available in Javascript with the "hasStart" attribute
1503 return self._hasStart
1505 def hasStartStopAll(self):
1506 """ Returns if this booking belongs to a plugin who has a "start" concept, and all of its bookings for a conference
1507 can be started simultanously.
1508 This attribute will be available in Javascript with the "hasStart" attribute
1510 return self._hasStartStopAll
1512 def hasStop(self):
1513 """ Returns if this booking belongs to a plugin who has a "stop" concept.
1514 This attribute will be available in Javascript with the "hasStop" attribute
1516 return self._hasStop
1518 def hasConnect(self):
1519 """ Returns if this booking belongs to a plugin who has a "connect" concept.
1520 This attribute will be available in Javascript with the "hasConnect" attribute
1522 if not hasattr(self, '_hasConnect'):
1523 self._hasConnect = False
1524 return self._hasConnect
1526 def hasCheckStatus(self):
1527 """ Returns if this booking belongs to a plugin who has a "check status" concept.
1528 This attribute will be available in Javascript with the "hasCheckStatus" attribute
1530 return self._hasCheckStatus
1532 def hasAcceptReject(self):
1533 """ Returns if this booking belongs to a plugin who has a "accept or reject" concept.
1534 This attribute will be available in Javascript with the "hasAcceptReject" attribute
1536 return self._hasAcceptReject
1538 def requiresServerCallForStart(self):
1539 """ Returns if this booking belongs to a plugin who requires a server call when the start button is pressed.
1540 This attribute will be available in Javascript with the "requiresServerCallForStart" attribute
1542 return self._requiresServerCallForStart
1544 def requiresServerCallForStop(self):
1545 """ Returns if this booking belongs to a plugin who requires a server call when the stop button is pressed.
1546 This attribute will be available in Javascript with the "requiresServerCallForStop" attribute
1548 return self._requiresServerCallForStop
1550 def requiresServerCallForConnect(self):
1551 """ Returns if this booking belongs to a plugin who requires a server call when the connect button is pressed.
1552 This attribute will be available in Javascript with the "requiresServerCallForConnect" attribute
1554 if not hasattr(self, '_requiresServerCallForConnect'):
1555 self._requiresServerCallForConnect = False
1556 return self._requiresServerCallForConnect
1558 def requiresClientCallForStart(self):
1559 """ Returns if this booking belongs to a plugin who requires a client call when the start button is pressed.
1560 This attribute will be available in Javascript with the "requiresClientCallForStart" attribute
1562 return self._requiresClientCallForStart
1564 def requiresClientCallForStop(self):
1565 """ Returns if this booking belongs to a plugin who requires a client call when the stop button is pressed.
1566 This attribute will be available in Javascript with the "requiresClientCallForStop" attribute
1568 return self._requiresClientCallForStop
1570 def requiresClientCallForConnect(self):
1571 """ Returns if this booking belongs to a plugin who requires a client call when the connect button is pressed.
1572 This attribute will be available in Javascript with the "requiresClientCallForConnect" attribute
1574 if not hasattr(self, '_requiresClientCallForConnect'):
1575 self._requiresClientCallForConnect = False
1576 return self._requiresClientCallForConnect
1578 def canBeDeleted(self):
1579 """ Returns if this booking can be deleted, in the sense that the "Remove" button will be active and able to be pressed.
1580 This attribute will be available in Javascript with the "canBeDeleted" attribute
1583 return self._canBeDeleted
1585 def setCanBeDeleted(self, canBeDeleted):
1586 """ Sets if this booking can be deleted, in the sense that the "Remove" button will be active and able to be pressed.
1587 This attribute will be available in Javascript with the "canBeDeleted" attribute
1589 self._canBeDeleted = canBeDeleted
1591 def canBeStarted(self):
1592 """ Returns if this booking can be started, in the sense that the "Start" button will be active and able to be pressed.
1593 This attribute will be available in Javascript with the "canBeStarted" attribute
1595 return self.isHappeningNow()
1597 def canBeStopped(self):
1598 """ Returns if this booking can be stopped, in the sense that the "Stop" button will be active and able to be pressed.
1599 This attribute will be available in Javascript with the "canBeStopped" attribute
1601 return self.isHappeningNow()
1603 def canBeConnected(self):
1604 """ Returns if this booking can be connected, in the sense that the "Connect" button will be active and able to be pressed.
1605 This attribute will be available in Javascript with the "canBeConnected" attribute
1607 return self.isHappeningNow()
1609 def isPermittedToStart(self):
1610 """ Returns if this booking is allowed to start, in the sense that it will be started after the "Start" button is pressed.
1611 For example a booking should not be permitted to start before a given time, even if the button is active.
1612 This attribute will be available in Javascript with the "isPermittedToStart" attribute
1614 return self._permissionToStart
1616 def isPermittedToStop(self):
1617 """ Returns if this booking is allowed to stop, in the sense that it will be started after the "Stop" button is pressed.
1618 This attribute will be available in Javascript with the "isPermittedToStop" attribute
1620 return self._permissionToStop
1622 def isPermittedToConnect(self):
1623 """ Returns if this booking is allowed to stop, in the sense that it will be connect after the "Connect" button is pressed.
1624 This attribute will be available in Javascript with the "isPermittedToConnect" attribute
1626 if not hasattr(self, '_permissionToConnect'):
1627 self._permissionToConnect = False
1628 return self._permissionToConnect
1630 def needsBookingParamsCheck(self):
1631 """ Returns if this booking belongs to a plugin that needs to verify the booking parameters.
1633 return self._needsBookingParamsCheck
1635 def needsToBeNotifiedOnView(self):
1636 """ Returns if this booking needs to be notified when someone views it (for example when the list of bookings is returned)
1638 return self._needsToBeNotifiedOnView
1640 def canBeNotifiedOfEventDateChanges(self):
1641 """ Returns if bookings of this type should be able to be notified
1642 of their owner Event changing start date, end date or timezone.
1644 return False
1646 def needsToBeNotifiedOfDateChanges(self):
1647 """ Returns if this booking in particular needs to be notified
1648 of their owner Event changing start date, end date or timezone.
1650 return self._needsToBeNotifiedOfDateChanges
1652 def setNeedsToBeNotifiedOfDateChanges(self, needsToBeNotifiedOfDateChanges):
1653 """ Sets if this booking in particular needs to be notified
1654 of their owner Event changing start date, end date or timezone.
1656 self._needsToBeNotifiedOfDateChanges = needsToBeNotifiedOfDateChanges
1658 def isHidden(self):
1659 """ Return if this booking is "hidden"
1660 A hidden booking will not appear in display pages
1662 if not hasattr(self, '_hidden'):
1663 self._hidden = False
1664 return self._hidden
1666 def setHidden(self, hidden):
1667 """ Sets if this booking is "hidden"
1668 A hidden booking will not appear in display pages
1669 hidden: a Boolean
1671 self._hidden = hidden
1673 def isAllowMultiple(self):
1674 """ Returns if this booking belongs to a type that allows multiple bookings per event.
1676 return self._allowMultiple
1678 def shouldBeIndexed(self):
1679 """ Returns if bookings of this type should be indexed
1681 return self._shouldBeIndexed
1683 def getCommonIndexes(self):
1684 """ Returns a list of strings with the names of the
1685 common (shared) indexes that bookings of this type want to
1686 be included in.
1688 return self._commonIndexes
1690 def getModificationURL(self):
1691 return urlHandlers.UHConfModifCollaboration.getURL(self.getConference(),
1692 secure = ContextManager.get('currentRH').use_https(),
1693 tab = CollaborationTools.getPluginTab(self.getPlugin()))
1695 def hasStartDate(self):
1696 """ Returns if bookings of this type have a start date
1697 (they may only have creation / modification date)
1699 return self._hasStartDate
1701 def hasTitle(self):
1702 """ Returns if bookings of this type have a title
1704 return self._hasTitle
1706 def hasEventDisplay(self):
1707 """ Returns if the type of this booking should display something on
1708 an event display page
1710 return self._hasEventDisplay
1712 def isAdminOnly(self):
1713 """ Returns if this booking / this booking's plugin pages should only be displayed
1714 to Server Admins, Video Service Admins, or the respective plugin admins.
1716 return self._adminOnly
1718 def _create(self):
1719 """ To be overriden by inheriting classes.
1720 This method is called when a booking is created, after setting the booking parameters.
1721 The plugin should decide if the booking is accepted or not.
1722 Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin,
1723 or a EVO HTTP server in the EVO case.
1725 raise CollaborationException("Method _create was not overriden for the plugin type " + str(self._type))
1727 def _modify(self, oldBookingParams):
1728 """ To be overriden by inheriting classes.
1729 This method is called when a booking is modifying, after setting the booking parameters.
1730 The plugin should decide if the booking is accepted or not.
1731 Often this will involve communication with another entity, like an MCU for the multi-point H.323 plugin
1732 or a EVO HTTP server in the EVO case.
1733 A dictionary with the previous booking params is passed. This dictionary is the one obtained
1734 by the method self.getBookingParams() before the new params input by the user are applied.
1736 raise CollaborationException("Method _modify was not overriden for the plugin type " + str(self._type))
1738 def _start(self):
1739 """ To be overriden by inheriting classes
1740 This method is called when the user presses the "Start" button in a plugin who has a "Start" concept
1741 and whose flag _requiresServerCallForStart is True.
1742 Often this will involve communication with another entity.
1744 if self.hasStart():
1745 raise CollaborationException("Method _start was not overriden for the plugin type " + str(self._type))
1746 else:
1747 pass
1749 def _stop(self):
1750 """ To be overriden by inheriting classes
1751 This method is called when the user presses the "Stop" button in a plugin who has a "Stop" concept
1752 and whose flag _requiresServerCallForStop is True.
1753 Often this will involve communication with another entity.
1755 if self.hasStop():
1756 raise CollaborationException("Method _stop was not overriden for the plugin type " + str(self._type))
1757 else:
1758 pass
1760 def _connect(self):
1761 """ To be overriden by inheriting classes
1762 This method is called when the user presses the "Connect" button in a plugin who has a "Connect" concept
1763 and whose flag _requiresServerCallForConnect is True.
1764 Often this will involve communication with another entity.
1766 if self.hasConnect():
1767 raise CollaborationException("Method _connect was not overriden for the plugin type " + str(self._type))
1768 else:
1769 pass
1771 def _checkStatus(self):
1772 """ To be overriden by inheriting classes
1773 This method is called when the user presses the "Check Status" button in a plugin who has a "check status" concept.
1774 Often this will involve communication with another entity.
1776 if self.hasCheckStatus():
1777 raise CollaborationException("Method _checkStatus was not overriden for the plugin type " + str(self._type))
1778 else:
1779 pass
1781 def _accept(self, user = None):
1782 """ To be overriden by inheriting classes
1783 This method is called when a user with privileges presses the "Accept" button
1784 in a plugin who has a "accept or reject" concept.
1785 Often this will involve communication with another entity.
1787 if self.hasAcceptReject():
1788 raise CollaborationException("Method _accept was not overriden for the plugin type " + str(self._type))
1789 else:
1790 pass
1792 def _reject(self):
1793 """ To be overriden by inheriting classes
1794 This method is called when a user with privileges presses the "Reject" button
1795 in a plugin who has a "accept or reject" concept.
1796 Often this will involve communication with another entity.
1798 if self.hasAcceptReject():
1799 raise CollaborationException("Method _reject was not overriden for the plugin type " + str(self._type))
1800 else:
1801 pass
1803 def _notifyOnView(self):
1804 """ To be overriden by inheriting classes
1805 This method is called when a user "sees" a booking, for example when the list of bookings is displayed.
1806 Maybe in this moment the booking wants to update its status.
1808 if self.needsToBeNotifiedOnView():
1809 raise CollaborationException("Method _notifyOnView was not overriden for the plugin type " + str(self._type))
1810 else:
1811 pass
1813 def _delete(self):
1814 """ To be overriden by inheriting classes
1815 This method is called whent he user removes a booking. Maybe the plugin will need to liberate
1816 ressources that were allocated to it.
1817 This method does not unregister the booking from the list of date change observer of the meeting
1819 raise CollaborationException("Method _delete was not overriden for the plugin type " + str(self._type))
1821 def _sendNotifications(self, operation):
1823 Sends a mail, wrapping it with ExternalOperationsManager
1825 ExternalOperationsManager.execute(self, "sendMail_" + operation, self._sendMail, operation)
1827 def _sendMail(self, operation):
1828 if operation == 'new':
1829 try:
1830 notification = mail.NewBookingNotification(self)
1831 GenericMailer.sendAndLog(notification, self._conf,
1832 "MaKaC/plugins/Collaboration/base.py",
1833 self._conf.getCreator())
1834 except Exception, e:
1835 Logger.get('VideoServ').error(
1836 """Could not send NewBookingNotification for booking with id %s of event with id %s, exception: %s""" %
1837 (self.getId(), self._conf.getId(), str(e)))
1838 raise
1840 elif operation == 'modify':
1841 try:
1842 notification = mail.BookingModifiedNotification(self)
1843 GenericMailer.sendAndLog(notification, self._conf,
1844 "MaKaC/plugins/Collaboration/base.py",
1845 self._conf.getCreator())
1846 except Exception, e:
1847 Logger.get('VideoServ').error(
1848 """Could not send BookingModifiedNotification for booking with id %s of event with id %s, exception: %s""" %
1849 (self.getId(), self._conf.getId(), str(e)))
1850 raise
1852 elif operation == 'remove':
1853 try:
1854 notification = mail.BookingDeletedNotification(self)
1855 GenericMailer.sendAndLog(notification, self._conf,
1856 "MaKaC/plugins/Collaboration/base.py",
1857 self._conf.getCreator())
1858 except Exception, e:
1859 Logger.get('VideoServ').error(
1860 """Could not send BookingDeletedNotification for booking with id %s of event with id %s, exception: %s""" %
1861 (self.getId(), self._conf.getId(), str(e)))
1862 raise
1864 def getPlayStatus(self):
1865 if not hasattr(self, '_play_status'):
1866 self._play_status = None
1867 return self._play_status
1869 class WCSTemplateBase(wcomponents.WTemplated):
1870 """ Base class for Collaboration templates.
1871 It stores the following attributes:
1872 _conf : the corresponding Conference object.
1873 _pluginName: the corresponding plugin ("EVO", "DummyPlugin", etc.).
1874 _XXXOptions: a dictionary whose values are the options of the plugin called pluginName.
1875 So, for example, if an EVO template inherits from this class, an attribute self._EVOOptions will be available.
1876 This class also overloads the _setTPLFile method so that Indico knows where each plugin's *.tpl files are.
1879 def __init__(self, pluginId):
1880 """ Constructor for the WCSTemplateBase class.
1881 conf: a Conference object
1882 plugin: the corresponding plugin
1884 self._plugin = CollaborationTools.getPlugin(pluginId)
1885 self._pluginId = self._plugin.getId()
1886 self._ph = PluginsHolder()
1888 setattr(self, "_" + self._pluginId + "Options", self._plugin.getOptions())
1890 def _setTPLFile(self, extension='tpl'):
1891 tplDir = os.path.join(self._plugin.getModule().__path__[0], "tpls")
1893 fname = "%s.%s" % (self.tplId, extension)
1894 self.tplFile = os.path.join(tplDir, fname)
1896 hfile = self._getSpecificTPL(os.path.join(tplDir,self._pluginId,'chelp'), self.tplId,extension='wohl')
1897 self.helpFile = os.path.join(tplDir,'chelp',hfile)
1900 class WCSPageTemplateBase(WCSTemplateBase):
1901 """ Base class for Collaboration templates for the create / modify booking form.
1904 def __init__(self, conf, pluginId, user):
1905 WCSTemplateBase.__init__(self, pluginId)
1906 self._conf = conf
1907 self._user = user
1910 class WJSBase(WCSTemplateBase):
1911 """ Base class for Collaboration templates for Javascript code template.
1912 It overloads _setTPLFile so that indico can find the Main.js, Extra.js and Indexing.js files.
1914 def __init__(self, conf, plugin, user):
1915 WCSTemplateBase.__init__(self, plugin)
1916 self._conf = conf
1917 self._user = user
1919 def _setTPLFile(self):
1920 WCSTemplateBase._setTPLFile(self, extension='js')
1921 self.helpFile = ''
1924 class WCSCSSBase(WCSTemplateBase):
1925 """ Base class for Collaboration templates for CSS code template
1926 It overloads _setTPLFile so that indico can find the style.css files.
1929 def _setTPLFile(self):
1930 tplDir = self._plugin.getModule().__path__[0]
1931 fname = "%s.css" % self.tplId
1932 self.tplFile = os.path.join(tplDir, fname)
1933 self.helpFile = ''
1936 class CSErrorBase(Fossilizable):
1937 fossilizes(ICSErrorBaseFossil)
1939 """ When _create, _modify or _remove want to return an error,
1940 they should return an error that inherits from this class
1943 def __init__(self):
1944 pass
1946 def getUserMessage(self):
1947 """ To be overloaded.
1948 Returns the string that will be shown to the user when this error will happen.
1950 raise CollaborationException("Method getUserMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
1952 def getLogMessage(self):
1953 """ To be overloaded.
1954 Returns the string that will be printed in Indico's log when this error will happen.
1956 raise CollaborationException("Method getLogMessage was not overriden for the a CSErrorBase object of class " + self.__class__.__name__)
1958 class CSSanitizationError(CSErrorBase): #already Fossilizable
1959 fossilizes(ICSSanitizationErrorFossil)
1961 """ Class used to return which fields have a sanitization error (invalid html / script tags)
1964 def __init__(self, invalidFields):
1965 self._invalidFields = invalidFields
1967 def invalidFields(self):
1968 return self._invalidFields
1971 class CollaborationException(MaKaCError):
1972 """ Error for the Collaboration System "core". Each plugin should declare their own EVOError, etc.
1974 def __init__(self, msg, area = 'Collaboration', inner = None):
1975 MaKaCError.__init__(self, msg, area)
1976 self._inner = inner
1978 def getInner(self):
1979 return self._inner
1981 def __str__(self):
1982 return MaKaCError.__str__(self) + '. Inner: ' + str(self._inner)
1984 class CollaborationServiceException(ServiceError):
1985 """ Error for the Collaboration System "core", for Service calls.
1987 def __init__(self, message, inner = None):
1988 ServiceError.__init__(self, "ERR-COLL", message, inner)
1990 class SpeakerStatusEnum:
1991 (NOEMAIL, NOTSIGNED, SIGNED, FROMFILE, PENDING, REFUSED) = xrange(6)
1993 class SpeakerWrapper(Persistent, Fossilizable):
1995 fossilizes(ISpeakerWrapperBaseFossil)
1997 def __init__(self, speaker, contId, requestType):
1998 self.status = not speaker.getEmail() and SpeakerStatusEnum.NOEMAIL or SpeakerStatusEnum.NOTSIGNED
1999 self.speaker = speaker
2000 self.contId = contId
2001 self.requestType = requestType
2002 self.reason = ""
2003 self.localFile = None
2004 self.dateAgreement = 0
2005 self.ipSignature = None
2006 self.modificationDate = nowutc()
2007 self.uniqueIdHash = md5("%s.%s"%(time.time(), self.getUniqueId())).hexdigest()
2009 def getUniqueId(self):
2010 return "%s.%s"%(self.contId, self.speaker.getId())
2012 def getUniqueIdHash(self):
2013 # to remove once saved
2014 if not hasattr(self, "uniqueIdHash"):#TODO: remove when safe
2015 return md5(self.getUniqueId()).hexdigest()
2016 else:
2017 return self.uniqueIdHash
2019 def getStatus(self):
2020 return self.status
2022 def setStatus(self, newStatus, ip=None):
2023 try:
2024 self.status = newStatus;
2025 if newStatus == SpeakerStatusEnum.SIGNED or newStatus == SpeakerStatusEnum.FROMFILE:
2026 self.dateAgreement = now_utc()
2027 if newStatus == SpeakerStatusEnum.SIGNED:
2028 self.ipSignature = ip
2029 except Exception, e:
2030 Logger.get('VideoServ').error("Exception while changing the speaker status. Exception: " + str(e))
2032 def getDateAgreementSigned(self):
2033 if hasattr(self, "dateAgreement"):#TODO: remove when safe
2034 return self.dateAgreement
2035 return 0
2037 def getIpAddressWhenSigned(self):
2038 if hasattr(self, "ipSignature"):#TODO: remove when safe
2039 return self.ipSignature
2040 return None
2042 def getRejectReason(self):
2043 if hasattr(self, "reason"):#TODO: remove when safe
2044 if self.status == SpeakerStatusEnum.REFUSED and hasattr(self, "reason"):
2045 return self.reason
2046 else:
2047 return "This speaker has not refused the agreement."
2048 else:
2049 return "Information not available."
2051 def setRejectReason(self, reason):
2052 if hasattr(self, "reason"):#TODO: remove when safe
2053 self.reason = reason
2055 def getObject(self):
2056 return self.speaker
2058 def getContId(self):
2059 return self.contId
2061 def getRequestType(self):
2062 if hasattr(self, "requestType"):#TODO: remove when safe
2063 return self.requestType
2064 return "NA"
2066 def setRequestType(self, type):
2067 self.requestType = type
2069 def getSpeakerId(self):
2070 return self.speaker.getId()
2072 def getLocalFile(self):
2074 If exists, return path to paper agreement
2076 if hasattr(self, "localFile"):#TODO: remove when safe
2077 return self.localFile
2079 def setLocalFile(self, localFile):
2081 Set localFile of paper agreement
2083 if hasattr(self, "localFile"):#TODO: remove when safe
2084 self.localFile = localFile
2086 def hasEmail(self):
2087 if self.speaker.getEmail():
2088 return True
2089 return False
2091 def getCategory(self):
2092 return None
2094 def getConference(self):
2095 return self.speaker.getConference()
2097 def getContribution(self):
2098 # if the conference is a lecture, the getContribution will fail.
2099 if self.getConference().getType() == "simple_event":
2100 return None
2101 else:
2102 return self.speaker.getContribution()
2104 def getSession(self):
2105 return None
2107 def getSubContribution(self):
2108 return None
2110 def getModificationDate(self):
2111 if hasattr(self, "modificationDate"): # TODO: remove when safe
2112 return self.modificationDate
2113 return None
2115 def setModificationDate(self):
2116 if hasattr(self, "modificationDate"): # TODO: remove when safe
2117 self.modificationDate = now_utc()
2119 def getLocator(self):
2120 return self.getContribution().getLocator()
2122 def triggerNotification(self):
2123 if self.getRequestType() in ('recording', 'webcast'):
2124 self._triggerNotification(self.getRequestType())
2125 elif self.getRequestType() == 'both':
2126 self._triggerNotification('recording')
2127 self._triggerNotification('webcast')
2129 def _triggerNotification(self, type):
2130 url = None
2131 if type == 'recording':
2132 url = CollaborationTools.getOptionValue('RecordingRequest', 'AgreementNotificationURL')
2133 elif type == 'webcast':
2134 url = CollaborationTools.getOptionValue('WebcastRequest', 'AgreementNotificationURL')
2135 if not url:
2136 return
2137 signed = None
2138 if self.getStatus() in (SpeakerStatusEnum.FROMFILE, SpeakerStatusEnum.SIGNED):
2139 signed = True
2140 elif self.getStatus() == SpeakerStatusEnum.REFUSED:
2141 signed = False
2142 spk = self.getObject()
2143 payload = {
2144 'confId': self.getConference().getId(),
2145 'contrib': self.getContId(),
2146 'type': type,
2147 'status': self.getStatus(),
2148 'signed': signed,
2149 'speaker': {
2150 'id': spk.getId(),
2151 'name': spk.getFullName(),
2152 'email': spk.getEmail()
2155 cl = Client()
2156 cl.enqueue(HTTPTask(url, {'data': json.dumps(payload)}))