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