1 # -*- coding: utf-8 -*-
4 ## This file is part of CDS Indico.
5 ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
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
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
,\
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
,\
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
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.
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.
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.
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
89 # list of speaker wrapper for a conference
90 self
._speakerWrapperList
= []
91 self
.updateSpeakerWrapperList()
94 """ Returns the Conference (the meeting) that owns this CSBookingManager object.
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
)):
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:
138 for pluginName
in filterByType
:
139 keys
.extend(self
._bookingsByType
.get(pluginName
, []))
141 keys
= self
._bookings
.keys()
143 if onlyPublic
and self
.getHiddenBookings():
145 keys
= keys
.difference(self
.getHiddenBookings())
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
155 for booking
in bookingList
:
156 if booking
.needsToBeNotifiedOnView():
158 booking
._notifyOnView
()
160 Logger
.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e
))
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,[])
180 booking
= self
._bookings
[blist
[0]]
183 booking
._notifyOnView
()
185 Logger
.get('VideoServ').error("Exception while notifying to a booking that it is being viewed. Exception: " + str(e
))
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
205 def checkVideoLink(self
, bookingParams
):
207 if bookingParams
.get('videoLinkType',"") == "session":
208 sessSlotId
= bookingParams
.get("videoLinkSession","")
210 regExp
= re
.match(r
"""(s[0-9a]*)(l[0-9]*)""", sessSlotId
)
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
)
217 raise CollaborationException(_('The session does not exist.'))
218 slot
= session
.getSlotById(slotId
)
220 raise CollaborationException(_('The session does not exist.'))
221 return slot
.getUniqueId()
223 elif bookingParams
.get('videoLinkType',"") == "contribution":
224 contId
= bookingParams
.get("videoLinkContribution","")
226 raise CollaborationException(_('No contribution has been passed when the type is contribution.'))
227 cont
= self
._conf
.getContributionById(contId
)
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()
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
):
289 raise CollaborationServiceException("Problem while creating a booking of type " + bookingType
)
291 newId
= self
._getNewBookingId
()
292 newBooking
.setId(newId
)
293 createResult
= newBooking
._create
()
294 if isinstance(createResult
, CSErrorBase
):
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')
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
):
350 CSBookingManager
._rollbackChanges
(booking
, oldBookingParams
, oldModificationDate
)
351 if isinstance(error
, CSErrorBase
):
353 raise CollaborationServiceException("Problem while modifying a booking of type " + booking
.getType())
355 modifyResult
= booking
._modify
(oldBookingParams
)
356 if isinstance(modifyResult
, CSErrorBase
):
357 CSBookingManager
._rollbackChanges
(booking
, oldBookingParams
, oldModificationDate
)
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.'))
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')
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
):
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')
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():
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():
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
()
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
):
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
)
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
)
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.
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:
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
))
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"))
587 def getManagers(self
):
588 if not hasattr(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.
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
):
632 for plugin
in self
.getManagers().iterkeys():
633 if self
.isPluginManager(plugin
, user
):
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():
644 self
._changeConfTitleInIndex
(booking
, oldTitle
, newTitle
)
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
)))
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():
677 self
._changeConfStartDateInIndex
(booking
, oldStartDate
, newStartDate
)
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
689 booking
.setStartDate(oldBookingStartDate
+ (newStartDate
- oldStartDate
) )
691 booking
.setEndDate(oldBookingEndDate
+ (newEndDate
- oldEndDate
) )
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()))
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
)))
707 booking
.setStartDate(oldBookingStartDate
)
708 booking
.setEndDate(oldBookingEndDate
)
709 problems
.append(CSBookingManager
._booking
2NotifyProblem
(booking
, modifyResult
))
710 elif startDateChanged
:
711 self
._changeStartDateInIndex
(booking
, oldBookingStartDate
, booking
.getStartDate())
713 if hasattr(booking
, "notifyEventDateChanges"):
715 booking
.notifyEventDateChanges(oldStartDate
, newStartDate
, oldEndDate
, newEndDate
)
717 Logger
.get('VideoServ').exception("Exception while notifying a plugin of an event date changed: " + str(e
))
720 ContextManager
.get('dateChangeNotificationProblems')['Collaboration'] = [
721 'Some Video Services bookings could not be moved:',
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
734 def notifyLocationChange(self
):
735 for booking
in self
.getBookingList():
736 if hasattr(booking
, "notifyLocationChange"):
738 booking
.notifyLocationChange()
740 Logger
.get('VideoServ').exception("Exception while notifying a plugin of a location change: " + str(e
))
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
750 message
.extend(["The dates of the ", booking
.getType(), " booking"])
751 if booking
.hasTitle():
752 message
.extend([': "', booking
._getTitle
(), '" (', booking
.getStartDateAsString(), ' - ', booking
.getEndDateAsString(), ')'])
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():
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
)
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()
787 for pluginName
in self
._bookingsByType
:
788 if pluginName
in pluginsWithEventDisplay
:
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
)
803 raise CollaborationServiceException("Problem while creating a test booking")
805 newId
= self
._getNewBookingId
()
806 newBooking
.setId(newId
)
807 createResult
= newBooking
._create
()
808 if isinstance(createResult
, CSErrorBase
):
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
()
819 def _notifyModification(self
):
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': {} }
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 {}
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
])
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
856 exclusion
= set(d
['recording'][cont
]) ^
set(contributions
[cont
])
858 del d
["recording"][cont
]
860 d
["recording"][cont
] = list(exclusion
)
862 exclusion
= set(d
['webcast'][cont
]) ^
set(contributions
[cont
])
864 del d
["webcast"][cont
]
866 d
["webcast"][cont
] = list(exclusion
)
868 d
["both"] = contributions
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":
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
))
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)
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
]:
907 sw
= self
.getSpeakerWrapperByUniqueId("%s.%s"%(cont
, spk
.getId()))
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)
920 newSw
= SpeakerWrapper(spk
, cont
, type)
921 if not newSw
.getObject().getEmail():
922 newSw
.setStatus(SpeakerStatusEnum
.NOEMAIL
)
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:
944 def areSignatureCompleted(self
):
946 for spkWrap
in self
._speakerWrapperList
:
947 if spkWrap
.getStatus() != SpeakerStatusEnum
.FROMFILE
and \
948 spkWrap
.getStatus() != SpeakerStatusEnum
.SIGNED
:
953 def getSpeakerWrapperListByStatus(self
, status
):
954 '''Return a list of SpeakerWrapper matching the status.
957 for spkWrap
in self
._speakerWrapperList
:
958 if spkWrap
.getStatus() == status
:
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"]
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())
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
)
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
):
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
):
1018 target
= self
.getVideoServicesById(uniqueId
)
1020 for service
in target
:
1021 if service
== videoService
:
1022 target
.remove(service
)
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
1045 if not self
.hasVideoService(uniqueId
):
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
1058 return self
.getVideoServices().has_key(uniqueId
)
1060 if self
.getVideoServices().has_key(uniqueId
):
1061 for serv
in self
.getVideoServicesById(uniqueId
):
1065 return self
.getVideoServices().has_key(uniqueId
)
1067 def isAnyRequestAccepted(self
):
1069 Return true if at least one between recording and webcast request
1073 rr
= self
.getSingleBooking("RecordingRequest")
1074 wr
= self
.getSingleBooking("WebcastRequest")
1077 value
= rr
.getAcceptRejectStatus()
1080 value
= value
or wr
.getAcceptRejectStatus()
1084 def isContributionReadyToBePublished(self
, contId
):
1085 if not hasattr(self
, "_speakerWrapperList"):#TODO: remove when safe
1086 self
.updateSpeakerWrapperList(True)
1089 for spkWrap
in self
._speakerWrapperList
:
1090 if spkWrap
.getContId() == contId
:
1092 if spkWrap
.getStatus() != SpeakerStatusEnum
.SIGNED
and \
1093 spkWrap
.getStatus() != SpeakerStatusEnum
.FROMFILE
:
1096 #The list has to have at least one spkWrap with the given contId
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.
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
1147 _hasStartDate
= True
1148 _hasEventDisplay
= False
1151 _complexParameters
= []
1152 _linkVideoType
= 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.
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.
1179 self
._type
= bookingType
1180 self
._plugin
= CollaborationTools
.getPlugin(self
._type
)
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...
1207 """ Returns the internal, per-conference id of the booking.
1208 This attribute will be available in Javascript with the "id" identifier.
1212 def setId(self
, id):
1213 """ Sets the internal, per-conference id of the booking
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())
1224 """ Returns the type of the booking, as a string: "EVO", "DummyPlugin"
1225 This attribute will be available in Javascript with the "type" identifier.
1229 def getConference(self
):
1230 """ Returns the owner of this CSBookingBase object, which is a Conference object representing the meeting.
1234 def setConference(self
, conf
):
1235 """ Sets the owner of this CSBookingBase object, which is a Conference object representing the meeting.
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
1298 self
._modificationDateTimestamp
= int(datetimeToUnixTimeInt(date
))
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.
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
)
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:
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
1363 self
._startDateTimestamp
= int(datetimeToUnixTimeInt(startDate
))
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)
1374 self
.setStartDate(setAdjustedDate(parseDateTime(startDateString
), self
._conf
))
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
):
1385 return self
.getStartDate() < now
and self
.getEndDate() > now
1387 def hasHappened(self
):
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:
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
1418 self
._endDateTimestamp
= int(datetimeToUnixTimeInt(endDate
))
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)
1429 self
.setEndDate(setAdjustedDate(parseDateTime(endDateString
), self
._conf
))
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()
1439 if self
.isHappeningNow():
1440 return _("Ready to start!")
1441 elif self
.hasHappened():
1442 return _("Already took place")
1444 return _("Booking created")
1446 return _("Conference started")
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"
1457 return "statusMessageOK"
1459 def accept(self
, user
= None):
1460 """ Sets this booking as accepted
1462 self
._acceptRejectStatus
= True
1465 def reject(self
, reason
):
1466 """ Sets this booking as rejected, and stores the reason
1468 self
._acceptRejectStatus
= False
1469 self
._rejectReason
= reason
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"
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()
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:
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
1550 self
._linkVideoType
= self
._linkVideoId
= None
1552 def getLocation(self
):
1553 return self
._conf
.getLocation().getName() if self
._conf
.getLocation() else ""
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.
1575 for k
, v
in self
.__class
__._simpleParameters
.iteritems():
1576 if k
in self
._bookingParams
:
1577 value
= self
._bookingParams
[k
]
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
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
]()
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"]
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
]()
1614 raise CollaborationServiceException("Tried to retrieve complex parameter " + str(paramName
) + " but the corresponding getter method " + getMethodName
+ " is not implemented")
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]}...
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
)
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
)
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
)
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]:
1683 v
= self
.__class
__._simpleParameters
[k
][0](v
)
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
)
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")
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
()
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")
1714 for k
, v
in params
.iteritems():
1715 if type(v
) == str and hasTags(v
):
1716 invalidFields
.append(k
)
1719 return CSSanitizationError(invalidFields
)
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")
1736 return templateClass(self
, tz
).getHTML()
1740 def _getLaunchDisplayInfo(self
):
1741 """ To be overloaded by plugins
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
))
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
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.
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
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
1921 def setHidden(self
, hidden
):
1922 """ Sets if this booking is "hidden"
1923 A hidden booking will not appear in display pages
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
1943 return self
._commonIndexes
1945 def index_instances(self
):
1951 def unindex_instances(self
):
1957 def index_talk(self
, talk
):
1963 def unindex_talk(self
, talk
):
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
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
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
))
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.
2024 raise CollaborationException("Method _start was not overriden for the plugin type " + str(self
._type
))
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.
2035 raise CollaborationException("Method _stop was not overriden for the plugin type " + str(self
._type
))
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
))
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
))
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
))
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
))
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
))
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':
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
)))
2119 elif operation
== 'modify':
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
)))
2131 elif operation
== 'remove':
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
)))
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')
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
()
2174 def getTalkSelectionList(self
):
2175 """ Returns the resultant list if it is present and populated. None if
2178 if not self
._hasTalkSelectionContent
():
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"
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
)
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
)
2247 def _setTPLFile(self
):
2248 WCSTemplateBase
._setTPLFile
(self
, extension
='js')
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
)
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
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
)
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
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()
2344 return self
.uniqueIdHash
2346 def getStatus(self
):
2349 def setStatus(self
, newStatus
, ip
=None):
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
2364 def getIpAddressWhenSigned(self
):
2365 if hasattr(self
, "ipSignature"):#TODO: remove when safe
2366 return self
.ipSignature
2369 def getRejectReason(self
):
2370 if hasattr(self
, "reason"):#TODO: remove when safe
2371 if self
.status
== SpeakerStatusEnum
.REFUSED
and hasattr(self
, "reason"):
2374 return "This speaker has not refused the agreement."
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
):
2385 def getContId(self
):
2388 def getRequestType(self
):
2389 if hasattr(self
, "requestType"):#TODO: remove when safe
2390 return self
.requestType
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
2414 if self
.speaker
.getEmail():
2418 def getCategory(self
):
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":
2429 return self
.speaker
.getContribution()
2431 def getSession(self
):
2434 def getSubContribution(self
):
2437 def getModificationDate(self
):
2438 if hasattr(self
, "modificationDate"): # TODO: remove when safe
2439 return self
.modificationDate
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):
2458 if type == 'recording':
2459 url
= CollaborationTools
.getOptionValue('RecordingRequest', 'AgreementNotificationURL')
2460 elif type == 'webcast':
2461 url
= CollaborationTools
.getOptionValue('WebcastRequest', 'AgreementNotificationURL')
2465 if self
.getStatus() in (SpeakerStatusEnum
.FROMFILE
, SpeakerStatusEnum
.SIGNED
):
2467 elif self
.getStatus() == SpeakerStatusEnum
.REFUSED
:
2469 spk
= self
.getObject()
2471 'confId': self
.getConference().getId(),
2472 'contrib': self
.getContId(),
2474 'status': self
.getStatus(),
2478 'name': spk
.getFullName(),
2479 'email': spk
.getEmail()
2483 cl
.enqueue(HTTPTask(url
, {'data': json
.dumps(payload
)}))