Example #1
0
 def setOwner(self, owner):
     """
     FINAL (not intented to be overriden)
     """
     oryg = self._p_changed
     self.__owner = Impersistant(owner)
     self._p_changed = oryg
Example #2
0
 def setOwner( self, owner ):
     """
     FINAL (not intented to be overriden)
     """
     oryg = self._p_changed
     self.__owner = Impersistant( owner )
     self._p_changed = oryg
Example #3
0
class RoomBase(object):
    """
    Generic room, Data Access Layer independant. 
    Represents physical room suitable for meetings and/or conferences. 
    """

    # !=> Properties are in the end of class definition

    # Management -------------------------------------------------------------

    def __init__(self):
        """
        Do NOT insert object into database in the constructor.
        """
        self._name = None
        self._photoId = None
        self._locationName = None

    def insert(self):
        """
        Inserts room into database (SQL: INSERT).
        """
        AvatarHolder().invalidateRoomManagerIdList()
        self.checkIntegrity()

    def update(self):
        """
        Updates room in database (SQL: UPDATE).
        """
        #AvatarHolder().invalidateRoomManagerIdList()
        self.checkIntegrity()

    def remove(self):
        """
        Removes room from database (SQL: DELETE).
        """
        pass

    def notifyAboutResponsibility(self):
        """
        FINAL (not intented to be overriden)
        Notifies (e-mails) previous and new responsible about 
        responsibility change. Called after creating/updating the room.
        """
        pass

    # Query ------------------------------------------------------------------

    @staticmethod
    def getRooms(*args, **kwargs):
        """ 
        Returns list of rooms meeting specified conditions.

        It is 'query by example'. You specify conditions by creating
        the object and passing it to the method.
        
        All arguments are optional:
        
        roomID - just a shortcut. Will return ONE room (not a list) or None.
        roomName - just a shortcut. Will return ONE room (not a list) or None.
        roomExample - example RoomBase object.
        reservationExample - example ReservationBase object. Represents reservation period.
        available - Bool, true if room must be available, false if must be booked, None if do not care
        freeText - str, room will be found if this string will be found anywhere in the object
            i.e. in equipment list, comments, responsible etc.
        minCapacity - Bool, defaults to False. If True, then rooms of capacity >= will be found.
            Otherwise capacity it looks for rooms with capacity within 20% range.
        allFast - Bool, defaults to False. If True, ALL active rooms will be returned 
            in ultra fast way, REGARDLESS of all other options.
        ownedBy - Avatar
        customAtts - for rooms with custom attributes.
                     rooms with no .customAtts attributes will be filtered out if this parameter is present
                     The customAtts attribute should be a list of dictionaries with the attributes "name", "allowEmpty", "filter".
                     "name" -> the name of the custom attribute
                     "allowEmpty" -> if we allow the custom attribute to be empty or not (empty = "" or string with only whitespaces)
                     "filter" -> a function to which we will pass the value of the custom attribute and has to return True or False.
                     If there is more than 1 dictionary in the list, it will be like doing an AND of the conditions they represent.
                     (see example 6)
        
        Examples:

        # 1. Get all rooms
        rooms = RoomBase.getRooms()
        
        # 2. Get all rooms with capacity about 30
        r = Factory.newRoom()
        r.capacity = 30
        rooms = RoomBase.getRooms( roomExample = r )
        
        # 3. Get all rooms reserved on the New Year 2007, 
        # which have capacity about 30, are at Meyrin site and have 'jean' in comments.

        r = Factory.newRoom()
        r.capacity = 30
        r.site = 'Meyrin'
        r.comments = 'jean'
        p = ReservationBase()
        p.startDT = datetime.datetime( 2007, 01, 01 )
        p.endDT = datetime.datetime( 2007, 01, 01 )
        p.repeatability = None
        
        rooms = RoomBase.getRooms( roomExample = r, reservationExample = p, available = False )

        # 4. Get all rooms containing "sex" in their attributes
        
        rooms = RoomBase.getRooms( freeText = 'sex' )
        
        # 5. Get room 'AT AMPHITHEATRE'

        oneRoom = RoomBase.getRooms( roomName = 'AT AMPHITHEATRE' )
        
        #6. Get rooms with a H.323 IP defined
        rooms = RoomBase.getRooms ( customAtts = [{"name":'H323 IP', "allowEmpty":False,
                                                   "filter": (lambda ip: validIP(ip))}])
        """
        # Simply redirect to the plugin
        from MaKaC.rb_factory import Factory
        return Factory.newRoom().getRooms(**kwargs)

    def getReservations(self, resvExample=None, archival=None):
        """ 
        FINAL (not intented to be overriden)
        Returns reservations of this room, meeting specified criteria.
        Look ReservationBase.getReservations for details.
        """
        # Simply redirect to the plugin
        from MaKaC.rb_factory import Factory
        from MaKaC.rb_reservation import ReservationBase

        return ReservationBase.getReservations(resvExample=resvExample,
                                               rooms=[self],
                                               archival=archival)

    def getLiveReservations(self, resvExample=None):
        """ 
        FINAL (not intented to be overriden)
        Returns valid, non archival reservations of this room,
        meeting specified criteria. Look ReservationBase.getReservations for details. 
        """
        from MaKaC.rb_factory import Factory
        from MaKaC.rb_reservation import ReservationBase

        if resvExample == None:
            resvExample = Factory.newReservation()
        resvExample.isCancelled = False
        resvExample.isRejected = False

        return ReservationBase.getReservations(resvExample=resvExample,
                                               rooms=[self],
                                               archival=False)

    def isAvailable(self, potentialReservation):
        """ 
        FINAL (not intented to be overriden)
        Checks whether the room is available for the potentialReservation. 
        potentialReservation is of type ReservationBase. It specifies the period.
        """
        from MaKaC.rb_reservation import ReservationBase
        if potentialReservation.getCollisions(boolResult=True):
            return False
        return True

    def getResponsible(self):
        """
        FINAL (not intented to be overriden)
        Returns responsible person (Avatar object). 
        """
        avatar = AvatarHolder().getById(
            self.responsibleId)  #match( { 'id': self.responsibleId } )[0]

        return avatar

    # Statistical ------------------------------------------------------------

    @staticmethod
    def getNumberOfRooms(**kwargs):
        """
        FINAL (not intented to be overriden)
        Returns total number of rooms in database.
        """
        name = kwargs.get('location',
                          Location.getDefaultLocation().friendlyName)
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfRooms(location=name)

    @staticmethod
    def getNumberOfActiveRooms(**kwargs):
        """
        FINAL (not intented to be overriden)
        Returns number of rooms that are active (not logicaly deleted).
        """
        name = kwargs.get('location',
                          Location.getDefaultLocation().friendlyName)
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfActiveRooms(location=name)

    @staticmethod
    def getNumberOfReservableRooms(**kwargs):
        """
        FINAL (not intented to be overriden)
        Returns number of rooms which can be reserved.
        """
        name = kwargs.get('location',
                          Location.getDefaultLocation().friendlyName)
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfReservableRooms(
            location=name)

    @staticmethod
    def getTotalSurfaceAndCapacity(**kwargs):
        """
        FINAL (not intented to be overriden)
        Returns (total_surface, total_capacity) of all Active rooms.
        """
        name = kwargs.get('location',
                          Location.getDefaultLocation().friendlyName)
        location = Location.parse(name)
        roomEx = location.factory.newRoom()
        roomEx.isActive = True
        roomEx.isReservable = True
        rooms = CrossLocationQueries.getRooms(roomExample=roomEx,
                                              location=name)
        totalSurface, totalCapacity = 0, 0
        for r in rooms:
            if r.surfaceArea:
                totalSurface += r.surfaceArea
            if r.capacity:
                totalCapacity += r.capacity
        return (totalSurface, totalCapacity)

    @staticmethod
    def getAverageOccupation(**kwargs):
        """
        FINAL (not intented to be overriden)
        Returns float <0, 1> representing how often - on the avarage - 
        the rooms are booked during the working hours. (1 == all the time, 0 == never).
        """

        name = kwargs.get('location',
                          Location.getDefaultLocation().friendlyName)

        # Get active, publically reservable rooms
        from MaKaC.rb_factory import Factory
        roomEx = Factory.newRoom()
        roomEx.isActive = True
        roomEx.isReservable = True

        rooms = CrossLocationQueries.getRooms(roomExample=roomEx,
                                              location=name)

        # Find collisions with last month period
        from MaKaC.rb_reservation import ReservationBase, RepeatabilityEnum
        resvEx = ReservationBase()
        now = datetime.now()
        resvEx.endDT = datetime(now.year, now.month, now.day, 17, 30)
        resvEx.startDT = resvEx.endDT - timedelta(
            30, 9 * 3600)  # - 30 days and 9 hours
        resvEx.repeatability = RepeatabilityEnum.daily
        collisions = resvEx.getCollisions(rooms=rooms)

        totalWorkingDays = 0
        weekends = 0
        for day in iterdays(resvEx.startDT, resvEx.endDT):
            if day.weekday() in [5, 6]:  # Skip Saturday and Sunday
                weekends += 1
                continue
            # if c.startDT is CERN Holiday: continue
            totalWorkingDays += 1

        booked = timedelta(0)
        for c in collisions:
            if c.startDT.weekday() in [5, 6]:  # Skip Saturday and Sunday
                continue
            # if c.startDT is CERN Holiday: continue
            booked = booked + (c.endDT - c.startDT)
        totalBookableTime = totalWorkingDays * 9 * len(rooms)  # Hours
        bookedTime = booked.days * 24 + 1.0 * booked.seconds / 3600  # Hours
        if totalBookableTime > 0:
            return bookedTime / totalBookableTime
        else:
            return 0  # Error (no rooms in db)

    def getMyAverageOccupation(self, period="pastmonth"):
        """
        FINAL (not intented to be overriden)
        Returns float <0, 1> representing how often - on the avarage - 
        the room is booked during the working hours. (1 == all the time, 0 == never).
        """
        # Find collisions with last month period
        from MaKaC.rb_reservation import ReservationBase, RepeatabilityEnum
        resvEx = ReservationBase()
        now = datetime.now()
        if period == "pastmonth":
            resvEx.endDT = datetime(now.year, now.month, now.day, 17, 30)
            resvEx.startDT = resvEx.endDT - timedelta(
                30, 9 * 3600)  # - 30 days and 9 hours
        elif period == "thisyear":
            resvEx.endDT = datetime(now.year, now.month, now.day, 17, 30)
            resvEx.startDT = datetime(now.year, 1, 1, 0, 0)
        resvEx.repeatability = RepeatabilityEnum.daily
        collisions = resvEx.getCollisions(rooms=[self])

        totalWorkingDays = 0
        weekends = 0
        for day in iterdays(resvEx.startDT, resvEx.endDT):
            if day.weekday() in [5, 6]:  # Skip Saturday and Sunday
                weekends += 1
                continue
            # if c.startDT is CERN Holiday: continue
            totalWorkingDays += 1

        booked = timedelta(0)
        for c in collisions:
            if c.startDT.weekday() in [5, 6]:  # Skip Saturday and Sunday
                continue
            # if c.startDT is CERN Holiday: continue
            booked = booked + (c.endDT - c.startDT)
        totalBookableTime = totalWorkingDays * 9  # Hours
        bookedTime = booked.days * 24 + 1.0 * booked.seconds / 3600  # Hours
        if totalBookableTime > 0:
            return bookedTime / totalBookableTime
        else:
            return 0

    # Equipment ------------------------------------------------------------

    def setEquipment(self, eq):
        """
        Sets (replaces) the equipment list with the new one.
        It may be list ['eq1', 'eq2', ...] or str 'eq1`eq2`eq3`...'
        """
        if isinstance(eq, list):
            self._equipment = '`'.join(eq)
            return
        elif isinstance(eq, str):
            self._equipment = eq
            return
        raise 'Invalid equipment list'

    def getEquipment(self):
        """
        Returns the room's equipment list.
        """
        return self._equipment.split('`')

    def insertEquipment(self, equipmentName):
        """ Adds new equipment to the room. """
        if len(self._equipment) > 0:
            self._equipment += '`'
        self._equipment += equipmentName

    def removeEquipment(self, equipmentName):
        """ Removes equipment from the room. """
        e = self.getEquipment()
        e.remove(equipmentName)
        self.setEquipment(e)

    def hasEquipment(self, equipmentName):
        return equipmentName in self._equipment

    def isCloseToBuilding(self, buildingNr):
        """ Returns true if room is close to the specified building """
        raise 'Not implemented'

    def belongsTo(self, user):
        """ Returns true if current CrbsUser is responsible for this room """
        raise 'Not implemented'

    # "System" ---------------------------------------------------------------

    def checkIntegrity(self):
        """
        FINAL (not intented to be overriden)
        Checks whether:
        - all required attributes has values
        - values are of correct type
        - semantic coherence (i.e. star date <= end date)
        """

        # list of errors
        errors = []

        # check presence and types of arguments
        # =====================================================
        if self.id != None:  # Only for existing objects
            checkPresence(self, errors, 'id', int)
        checkPresence(self, errors, '_locationName', str)
        # check semantic integrity
        # =====================================================

        if errors:
            raise str(errors)

    # Photos -----------------------------------------------------------------

    # NOTE: In general, URL generation should be in urlHandlers.
    # This exception is because we want to allow other room booking systems
    # to override room photos.

    def getPhotoURL(self):
        # Used to send photos via Python script
        #from MaKaC.webinterface.urlHandlers import UHSendRoomPhoto
        #return UHSendRoomPhoto.getURL( self.photoId, small = False )
        from MaKaC.webinterface.urlHandlers import UHRoomPhoto
        return UHRoomPhoto.getURL(self.photoId)

    def getSmallPhotoURL(self):
        # Used to send photos via Python script
        #from MaKaC.webinterface.urlHandlers import UHSendRoomPhoto
        #return UHSendRoomPhoto.getURL( self.photoId, small = True )
        from MaKaC.webinterface.urlHandlers import UHRoomPhotoSmall
        return UHRoomPhotoSmall.getURL(self.photoId)

    def savePhoto(self, photoPath):
        """
        Saves room's photo on the server.
        """
        pass

    def saveSmallPhoto(self, photoPath):
        """
        Saves room's small photo on the server.
        """
        pass

    # Indico architecture ----------------------------------------------------

    __owner = None

    def getLocator(self):
        """
        FINAL (not intented to be overriden)
        Returns a globaly unique identification encapsulated in a Locator object
        """
        owner = self.getOwner()
        if owner:
            loc = owner.getLocator()
        else:
            from MaKaC.common.Locators import Locator
            loc = Locator()
        loc["roomLocation"] = self.locationName
        loc["roomID"] = self.id
        return loc

    def setOwner(self, owner):
        """
        FINAL (not intented to be overriden)
        """
        oryg = self._p_changed
        self.__owner = Impersistant(owner)
        self._p_changed = oryg

    def getOwner(self):
        """
        FINAL (not intented to be overriden)
        """
        if self.__owner:
            return self.__owner.getObject()  # Wrapped in Impersistent
        return None

    def isProtected(self):
        """
        FINAL (not intented to be overriden)
        The one must be logged in to do anything in RB module.
        """
        return True

    def canView(self, accessWrapper):
        """
        FINAL (not intented to be overriden)
        Room details are public - anyone can view.
        """
        return True

    def canBook(self, user):
        """
        FINAL (not intented to be overriden)
        Reservable rooms which does not require pre-booking can be booked by anyone.
        Other rooms - only by their responsibles.
        """
        if self.isActive and self.isReservable and not self.resvsNeedConfirmation:
            if self.customAtts.get('Booking Simba List'):
                list = self.customAtts.get('Booking Simba List')
                if list != "Error: unknown mailing list" and list != "":
                    if user.isMemberOfSimbaList(list):
                        return True
            else:
                return True
        if user == None:
            return False
        if (self.isOwnedBy( user ) and self.isActive) \
               or user.isAdmin():
            return True
        return False

    def canPrebook(self, user):
        """
        FINAL (not intented to be overriden)
        Reservable rooms can be pre-booked by anyone.
        Other rooms - only by their responsibles.
        """
        if self.isActive and self.isReservable:
            if self.customAtts.get('Booking Simba List'):
                list = self.customAtts.get('Booking Simba List')
                if list != "Error: unknown mailing list" and list != "":
                    if user.isMemberOfSimbaList(list):
                        return True
            else:
                return True
        if user == None:
            return False
        if (self.isOwnedBy( user ) and self.isActive) \
               or user.isAdmin():
            return True
        return False

    def canModify(self, accessWrapper):
        """
        FINAL (not intented to be overriden)
        Only admin can modify rooms.
        """
        if accessWrapper == None:
            return False
        if isinstance(accessWrapper, AccessWrapper):
            if accessWrapper.getUser():
                return accessWrapper.getUser().isAdmin()
            else:
                return False
        elif isinstance(accessWrapper, Avatar):
            return accessWrapper.isAdmin()

        raise 'canModify requires either AccessWrapper or Avatar object'

    def canDelete(self, user):
        return self.canModify(user)

    def isOwnedBy(self, user):
        """
        Returns True if user is responsible for this room. False otherwise.
        """
        if not self.responsibleId:
            return None
        if self.responsibleId == user.id:
            return True
        try:
            if user in self._v_isOwnedBy.keys():
                return self._v_isOwnedBy[user]
        except:
            self._v_isOwnedBy = {}
        if self.customAtts.get('Simba List'):
            list = self.customAtts.get('Simba List')
            if list != "Error: unknown mailing list" and list != "":
                if user.isMemberOfSimbaList(list):
                    self._v_isOwnedBy[user] = True
                    return True
        self._v_isOwnedBy[user] = False
        return False

    def getLocationName(self):
        if self.__class__.__name__ == 'RoomBase':
            return Location.getDefaultLocation().friendlyName
            #raise 'This method is purely virtual. Call it only on derived objects.'
        return self.getLocationName()  # Subclass

    def setLocationName(self, locationName):
        if self.__class__.__name__ == 'RoomBase':
            raise 'This method is purely virtual. Call it only on derived objects.'
        return self.setLocationName(locationName)  # Subclass

    def getAccessKey(self):
        return ""

    def getFullName(self):
        name = ""
        if self.building != None and self.floor != None and self.building != None:
            s = str(self.building) + '-' + str(self.floor) + '-' + str(
                self.roomNr)
            if s != '--':
                name = s
        if self._name != None and len(self._name.strip()) > 0:
            name += " - %s" % self._name
        return name

    # ==== Private ===================================================

    _name = None
    _equipment = ''  # str, 'eq1`eq2`eq3' - list of room's equipment, joined by '`'

    def _getGuid(self):
        if self.id == None or self.locationName == None:
            return None
        return RoomGUID(Location.parse(self.locationName), self.id)

    def _getName(self):
        if self._name != None and len(self._name.strip()) > 0:
            return self._name
        if self.building != None and self.floor != None and self.building != None:
            s = str(self.building) + '-' + str(self.floor) + '-' + str(
                self.roomNr)
            if s != '--':
                return s
            return ''
        return None

    def _setName(self, s):
        # Try to parse the name
        if s == None:
            self._name = None
            return
        parts = s.split('-')
        if len(parts) == 3:
            try:
                self.building = int(parts[0])
                self.floor = parts[1]
                self.roomNr = parts[2]
                return
            except:
                pass
        # Parsing failed, that means it is real name
        self._name = s

    # CERN specific; don't bother
    def _getNeedsAVCSetup(self):
        eq = self.getEquipment()
        if not self.locationName or not eq:
            return None
        return 'Video conference' in ' '.join(eq)

    def _eval_str(self, s):
        ixPrv = 0
        ret = ""

        while True:
            ix = s.find("#{", ixPrv)
            if ix == -1:
                break
            ret += s[ixPrv:ix]  # verbatim
            ixPrv = s.index("}", ix + 2) + 1
            ret += str(eval(s[ix + 2:ixPrv - 1]))
        ret += s[ixPrv:len(s)]

        return ret

    def _getVerboseEquipment(self):
        s = ""
        eqList = self.getEquipment()
        for eq in eqList:
            s = s + eq + ", "
        if len(eqList) > 0: s = s[0:len(s) - 2]  # Cut off last ','
        return s

    def _getPhotoId(self):
        """ 
        Feel free to override this in your inherited class.
        """
        return self._doGetPhotoId()

    def _setPhotoId(self, value):
        self._photoId = value

    def _doGetPhotoId(self):
        if '_photoId' in dir(self): return self._photoId
        return None

    def __str__(self):
        s = self._eval_str("""
               id: #{self.id}
         isActive: #{self.isActive}

             room: #{self.name}
             
         building: #{self.building}
            floor: #{self.floor}
           roomNr: #{self.roomNr}
     isReservable: #{self.isReservable}
rNeedConfirmation: #{self.resvsNeedConfirmation}

             site: #{self.site}
         capacity: #{self.capacity}
      surfaceArea: #{self.surfaceArea}
         division: #{self.division}
          photoId: #{self.photoId}
       externalId: #{self.externalId}

        telephone: #{self.telephone}
       whereIsKey: #{self.whereIsKey}
         comments: #{self.comments}
    responsibleId: #{self.responsibleId}
        equipment: """)
        s += self.verboseEquipment + "\n"
        return s

    def __cmp__(self, other):
        if self.__class__.__name__ == 'NoneType' and other.__class__.__name__ == 'NoneType':
            return 0
        if self.__class__.__name__ == 'NoneType':
            return cmp(None, 1)
        if other.__class__.__name__ == 'NoneType':
            return cmp(1, None)

        if self.id != None and other.id != None:
            if self.id == other.id:
                return 0

        c = cmp(self.locationName, other.locationName)
        if c == 0:
            c = cmp(self.building, other.building)
            if c == 0:
                c = cmp(self.floor, other.floor)
                if c == 0:
                    c = cmp(self.name, other.name)

        return c

    # ==== Properties ===================================================

    # DO NOT set default values here, since query-by-example will change!!!

    id = None  # int - artificial ID; initialy value from oracle db
    locationName = property(getLocationName,
                            setLocationName)  # location (plugin) name
    guid = property(_getGuid)  # RoomGUID
    isActive = None  # bool - whether the room is active (not logicaly removed) [STSCRBOK]
    resvsNeedConfirmation = None  # bool - whether reservations for this room must be confirmed by responsible

    building = None  # int, positive
    floor = None  # str, alphanumeric
    roomNr = None  # str

    name = property(_getName, _setName)  # str - room name

    capacity = None  # int, positive
    site = None  # str - global room localisation, i.e. city
    division = None  # str, TODO
    isReservable = None  # bool - whether the room is reservable
    photoId = property(_getPhotoId, _setPhotoId)  # str - room picture id
    externalId = None  # str - custom external room id, i.e. for locating on the map

    telephone = None  # str
    surfaceArea = None  # int, positive - in meters^2
    whereIsKey = None  # str, typically telephone number
    comments = None  # str
    responsibleId = None  # str, responsible person id (avatar.id)

    #customAtts = {}      # Must behave like name-value dictionary of
    # custom attributes. Must be put in derived classes.

    verboseEquipment = property(_getVerboseEquipment)
    needsAVCSetup = property(_getNeedsAVCSetup)
Example #4
0
class RoomBase( object ):
    """
    Generic room, Data Access Layer independant. 
    Represents physical room suitable for meetings and/or conferences. 
    """
    
    # !=> Properties are in the end of class definition

    # Management -------------------------------------------------------------
    
    def __init__( self ):
        """
        Do NOT insert object into database in the constructor.
        """
        self._name = None
        self._photoId = None
        self._locationName = None

    def insert( self ):
        """
        Inserts room into database (SQL: INSERT).
        """
        AvatarHolder().invalidateRoomManagerIdList()
        self.checkIntegrity()
    
    def update( self ):
        """
        Updates room in database (SQL: UPDATE).
        """
        #AvatarHolder().invalidateRoomManagerIdList()
        self.checkIntegrity()
    
    def remove( self ):
        """
        Removes room from database (SQL: DELETE).
        """
        pass
    
    def notifyAboutResponsibility( self ):
        """
        FINAL (not intented to be overriden)
        Notifies (e-mails) previous and new responsible about 
        responsibility change. Called after creating/updating the room.
        """
        pass 
    
    # Query ------------------------------------------------------------------
    
    @staticmethod
    def getRooms( *args, **kwargs ):
        """ 
        Returns list of rooms meeting specified conditions.

        It is 'query by example'. You specify conditions by creating
        the object and passing it to the method.
        
        All arguments are optional:
        
        roomID - just a shortcut. Will return ONE room (not a list) or None.
        roomName - just a shortcut. Will return ONE room (not a list) or None.
        roomExample - example RoomBase object.
        reservationExample - example ReservationBase object. Represents reservation period.
        available - Bool, true if room must be available, false if must be booked, None if do not care
        freeText - str, room will be found if this string will be found anywhere in the object
            i.e. in equipment list, comments, responsible etc.
        minCapacity - Bool, defaults to False. If True, then rooms of capacity >= will be found.
            Otherwise capacity it looks for rooms with capacity within 20% range.
        allFast - Bool, defaults to False. If True, ALL active rooms will be returned 
            in ultra fast way, REGARDLESS of all other options.
        ownedBy - Avatar
        customAtts - for rooms with custom attributes.
                     rooms with no .customAtts attributes will be filtered out if this parameter is present
                     The customAtts attribute should be a list of dictionaries with the attributes "name", "allowEmpty", "filter".
                     "name" -> the name of the custom attribute
                     "allowEmpty" -> if we allow the custom attribute to be empty or not (empty = "" or string with only whitespaces)
                     "filter" -> a function to which we will pass the value of the custom attribute and has to return True or False.
                     If there is more than 1 dictionary in the list, it will be like doing an AND of the conditions they represent.
                     (see example 6)
        
        Examples:

        # 1. Get all rooms
        rooms = RoomBase.getRooms()
        
        # 2. Get all rooms with capacity about 30
        r = Factory.newRoom()
        r.capacity = 30
        rooms = RoomBase.getRooms( roomExample = r )
        
        # 3. Get all rooms reserved on the New Year 2007, 
        # which have capacity about 30, are at Meyrin site and have 'jean' in comments.

        r = Factory.newRoom()
        r.capacity = 30
        r.site = 'Meyrin'
        r.comments = 'jean'
        p = ReservationBase()
        p.startDT = datetime.datetime( 2007, 01, 01 )
        p.endDT = datetime.datetime( 2007, 01, 01 )
        p.repeatability = None
        
        rooms = RoomBase.getRooms( roomExample = r, reservationExample = p, available = False )

        # 4. Get all rooms containing "sex" in their attributes
        
        rooms = RoomBase.getRooms( freeText = 'sex' )
        
        # 5. Get room 'AT AMPHITHEATRE'

        oneRoom = RoomBase.getRooms( roomName = 'AT AMPHITHEATRE' )
        
        #6. Get rooms with a H.323 IP defined
        rooms = RoomBase.getRooms ( customAtts = [{"name":'H323 IP', "allowEmpty":False,
                                                   "filter": (lambda ip: validIP(ip))}])
        """
        # Simply redirect to the plugin
        from MaKaC.rb_factory import Factory
        return Factory.newRoom().getRooms( **kwargs )

    def getReservations( self, resvExample = None, archival = None ):
        """ 
        FINAL (not intented to be overriden)
        Returns reservations of this room, meeting specified criteria.
        Look ReservationBase.getReservations for details.
        """
        # Simply redirect to the plugin
        from MaKaC.rb_factory import Factory
        from MaKaC.rb_reservation import ReservationBase
        
        return ReservationBase.getReservations( resvExample = resvExample, rooms = [self], archival = archival )
    
    def getLiveReservations( self, resvExample = None ):
        """ 
        FINAL (not intented to be overriden)
        Returns valid, non archival reservations of this room,
        meeting specified criteria. Look ReservationBase.getReservations for details. 
        """
        from MaKaC.rb_factory import Factory
        from MaKaC.rb_reservation import ReservationBase
        
        if resvExample == None:
            resvExample = Factory.newReservation()
        resvExample.isCancelled = False
        resvExample.isRejected = False
        
        return ReservationBase.getReservations( resvExample = resvExample, 
                                                rooms = [self], archival = False )
    
    def isAvailable( self, potentialReservation ):
        """ 
        FINAL (not intented to be overriden)
        Checks whether the room is available for the potentialReservation. 
        potentialReservation is of type ReservationBase. It specifies the period.
        """
        from MaKaC.rb_reservation import ReservationBase
        if potentialReservation.getCollisions( boolResult = True ):
            return False
        return True

    def getResponsible( self ):
        """
        FINAL (not intented to be overriden)
        Returns responsible person (Avatar object). 
        """
        avatar = AvatarHolder().getById( self.responsibleId )#match( { 'id': self.responsibleId } )[0]
        
        return avatar
    
    # Statistical ------------------------------------------------------------

    @staticmethod
    def getNumberOfRooms( **kwargs ):
        """
        FINAL (not intented to be overriden)
        Returns total number of rooms in database.
        """
        name = kwargs.get( 'location', Location.getDefaultLocation().friendlyName )
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfRooms(location=name)
    
    @staticmethod
    def getNumberOfActiveRooms( **kwargs ):
        """
        FINAL (not intented to be overriden)
        Returns number of rooms that are active (not logicaly deleted).
        """
        name = kwargs.get( 'location', Location.getDefaultLocation().friendlyName )
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfActiveRooms(location=name)

    @staticmethod
    def getNumberOfReservableRooms( **kwargs ):
        """
        FINAL (not intented to be overriden)
        Returns number of rooms which can be reserved.
        """
        name = kwargs.get( 'location', Location.getDefaultLocation().friendlyName )
        location = Location.parse(name)
        return location.factory.newRoom().getNumberOfReservableRooms(location=name)

    @staticmethod
    def getTotalSurfaceAndCapacity( **kwargs ):
        """
        FINAL (not intented to be overriden)
        Returns (total_surface, total_capacity) of all Active rooms.
        """
        name = kwargs.get( 'location', Location.getDefaultLocation().friendlyName )
        location = Location.parse(name)
        roomEx = location.factory.newRoom()
        roomEx.isActive = True
        roomEx.isReservable = True
        rooms = CrossLocationQueries.getRooms( roomExample = roomEx, location = name )
        totalSurface, totalCapacity = 0, 0
        for r in rooms:
            if r.surfaceArea:
                totalSurface += r.surfaceArea
            if r.capacity:
                totalCapacity += r.capacity
        return ( totalSurface, totalCapacity )
    
    @staticmethod
    def getAverageOccupation( **kwargs ):
        """
        FINAL (not intented to be overriden)
        Returns float <0, 1> representing how often - on the avarage - 
        the rooms are booked during the working hours. (1 == all the time, 0 == never).
        """
        
        name = kwargs.get( 'location', Location.getDefaultLocation().friendlyName )
        
        # Get active, publically reservable rooms
        from MaKaC.rb_factory import Factory
        roomEx = Factory.newRoom()
        roomEx.isActive = True
        roomEx.isReservable = True
        
        rooms = CrossLocationQueries.getRooms( roomExample = roomEx, location = name )
        
        # Find collisions with last month period
        from MaKaC.rb_reservation import ReservationBase, RepeatabilityEnum
        resvEx = ReservationBase()
        now = datetime.now()
        resvEx.endDT = datetime( now.year, now.month, now.day, 17, 30 )
        resvEx.startDT = resvEx.endDT - timedelta( 30, 9 * 3600 ) # - 30 days and 9 hours
        resvEx.repeatability = RepeatabilityEnum.daily
        collisions = resvEx.getCollisions( rooms = rooms )

        totalWorkingDays = 0
        weekends = 0
        for day in iterdays( resvEx.startDT, resvEx.endDT ):
            if day.weekday() in [5,6]: # Skip Saturday and Sunday
                weekends += 1
                continue
            # if c.startDT is CERN Holiday: continue
            totalWorkingDays += 1

        booked = timedelta( 0 )
        for c in collisions:
            if c.startDT.weekday() in [5,6]: # Skip Saturday and Sunday
                continue
            # if c.startDT is CERN Holiday: continue
            booked = booked + ( c.endDT - c.startDT )
        totalBookableTime = totalWorkingDays * 9 * len( rooms ) # Hours
        bookedTime = booked.days * 24 + 1.0 * booked.seconds / 3600 # Hours
        if totalBookableTime > 0:
            return bookedTime / totalBookableTime
        else:
            return 0 # Error (no rooms in db)

   
    def getMyAverageOccupation( self, period="pastmonth" ):
        """
        FINAL (not intented to be overriden)
        Returns float <0, 1> representing how often - on the avarage - 
        the room is booked during the working hours. (1 == all the time, 0 == never).
        """
        # Find collisions with last month period
        from MaKaC.rb_reservation import ReservationBase, RepeatabilityEnum
        resvEx = ReservationBase()
        now = datetime.now()
        if period == "pastmonth":
            resvEx.endDT = datetime( now.year, now.month, now.day, 17, 30 )
            resvEx.startDT = resvEx.endDT - timedelta( 30, 9 * 3600 ) # - 30 days and 9 hours
        elif period == "thisyear":
            resvEx.endDT = datetime( now.year, now.month, now.day, 17, 30 )
            resvEx.startDT = datetime( now.year, 1, 1, 0, 0 )
        resvEx.repeatability = RepeatabilityEnum.daily
        collisions = resvEx.getCollisions( rooms = [self] )

        totalWorkingDays = 0
        weekends = 0
        for day in iterdays( resvEx.startDT, resvEx.endDT ):
            if day.weekday() in [5,6]: # Skip Saturday and Sunday
                weekends += 1
                continue
            # if c.startDT is CERN Holiday: continue
            totalWorkingDays += 1

        booked = timedelta( 0 )
        for c in collisions:
            if c.startDT.weekday() in [5,6]: # Skip Saturday and Sunday
                continue
            # if c.startDT is CERN Holiday: continue
            booked = booked + ( c.endDT - c.startDT )
        totalBookableTime = totalWorkingDays * 9 # Hours
        bookedTime = booked.days * 24 + 1.0 * booked.seconds / 3600 # Hours
        if totalBookableTime > 0:
            return bookedTime / totalBookableTime
        else:
            return 0
        

    # Equipment ------------------------------------------------------------

    def setEquipment( self, eq ):
        """
        Sets (replaces) the equipment list with the new one.
        It may be list ['eq1', 'eq2', ...] or str 'eq1`eq2`eq3`...'
        """
        if isinstance( eq, list ):
            self._equipment = '`'.join( eq )
            return
        elif isinstance( eq, str ):
            self._equipment = eq
            return
        raise 'Invalid equipment list'
        
    def getEquipment( self ):
        """
        Returns the room's equipment list.
        """
        return self._equipment.split( '`' )
    
    def insertEquipment( self, equipmentName ):
        """ Adds new equipment to the room. """
        if len( self._equipment ) > 0:
            self._equipment += '`' 
        self._equipment += equipmentName
    
    def removeEquipment( self, equipmentName ):
        """ Removes equipment from the room. """
        e = self.getEquipment()
        e.remove( equipmentName )
        self.setEquipment( e )
    
    def hasEquipment( self, equipmentName ):
        return equipmentName in self._equipment

    def isCloseToBuilding( self, buildingNr ):
        """ Returns true if room is close to the specified building """
        raise 'Not implemented'
    
    def belongsTo( self, user ):
        """ Returns true if current CrbsUser is responsible for this room """
        raise 'Not implemented'
   
    # "System" ---------------------------------------------------------------
    
    def checkIntegrity( self ):
        """
        FINAL (not intented to be overriden)
        Checks whether:
        - all required attributes has values
        - values are of correct type
        - semantic coherence (i.e. star date <= end date)
        """
        
        # list of errors
        errors = []
        
        # check presence and types of arguments
        # =====================================================
        if self.id != None:         # Only for existing objects
            checkPresence( self, errors, 'id', int )
        checkPresence( self, errors, '_locationName', str )
        # check semantic integrity
        # =====================================================
        
        if errors:
            raise str( errors )

    # Photos -----------------------------------------------------------------

    # NOTE: In general, URL generation should be in urlHandlers. 
    # This exception is because we want to allow other room booking systems 
    # to override room photos.
    
    def getPhotoURL( self ):
        # Used to send photos via Python script
        #from MaKaC.webinterface.urlHandlers import UHSendRoomPhoto
        #return UHSendRoomPhoto.getURL( self.photoId, small = False )
        from MaKaC.webinterface.urlHandlers import UHRoomPhoto
        return UHRoomPhoto.getURL( self.photoId )
    
    def getSmallPhotoURL( self ):
        # Used to send photos via Python script
        #from MaKaC.webinterface.urlHandlers import UHSendRoomPhoto
        #return UHSendRoomPhoto.getURL( self.photoId, small = True )
        from MaKaC.webinterface.urlHandlers import UHRoomPhotoSmall
        return UHRoomPhotoSmall.getURL( self.photoId )

    def savePhoto( self, photoPath ):
        """
        Saves room's photo on the server.
        """
        pass
    
    def saveSmallPhoto( self, photoPath ):
        """
        Saves room's small photo on the server.
        """
        pass

    # Indico architecture ----------------------------------------------------

    __owner = None

    def getLocator( self ):
        """
        FINAL (not intented to be overriden)
        Returns a globaly unique identification encapsulated in a Locator object
        """
        owner = self.getOwner()
        if owner:
            loc = owner.getLocator()
        else:
            from MaKaC.common.Locators import Locator
            loc = Locator()
        loc["roomLocation"] = self.locationName
        loc["roomID"] = self.id
        return loc

    def setOwner( self, owner ):
        """
        FINAL (not intented to be overriden)
        """
        oryg = self._p_changed
        self.__owner = Impersistant( owner )
        self._p_changed = oryg

    def getOwner( self ):
        """
        FINAL (not intented to be overriden)
        """
        if self.__owner:
            return self.__owner.getObject() # Wrapped in Impersistent
        return None

    def isProtected( self ):
        """
        FINAL (not intented to be overriden)
        The one must be logged in to do anything in RB module.
        """
        return True
    
    def canView( self, accessWrapper ):
        """
        FINAL (not intented to be overriden)
        Room details are public - anyone can view.
        """
        return True
    
    def canBook( self, user ):
        """
        FINAL (not intented to be overriden)
        Reservable rooms which does not require pre-booking can be booked by anyone.
        Other rooms - only by their responsibles.
        """
        if self.isActive and self.isReservable and not self.resvsNeedConfirmation:
            if self.customAtts.get( 'Booking Simba List' ):
                list = self.customAtts.get( 'Booking Simba List' )
                if list != "Error: unknown mailing list" and list != "":
                    if user.isMemberOfSimbaList( list ):
                        return True
            else:
                return True
        if user == None:
            return False
        if (self.isOwnedBy( user ) and self.isActive) \
               or user.isAdmin():
            return True
        return False    
    
    def canPrebook( self, user ):
        """
        FINAL (not intented to be overriden)
        Reservable rooms can be pre-booked by anyone.
        Other rooms - only by their responsibles.
        """    
        if self.isActive and self.isReservable:
            if self.customAtts.get( 'Booking Simba List' ):
                list = self.customAtts.get( 'Booking Simba List' )
                if list != "Error: unknown mailing list" and list != "":
                    if user.isMemberOfSimbaList( list ):
                        return True
            else:
                return True
        if user == None:
            return False
        if (self.isOwnedBy( user ) and self.isActive) \
               or user.isAdmin():
            return True
        return False

    def canModify( self, accessWrapper ):
        """
        FINAL (not intented to be overriden)
        Only admin can modify rooms.
        """
        if accessWrapper == None:
            return False
        if isinstance( accessWrapper, AccessWrapper ):
            if accessWrapper.getUser():
                return accessWrapper.getUser().isAdmin()
            else:
                return False
        elif isinstance( accessWrapper, Avatar ):
            return accessWrapper.isAdmin()

        raise 'canModify requires either AccessWrapper or Avatar object'
    
    def canDelete( self, user ):
        return self.canModify( user )
    
    def isOwnedBy( self, user ):
        """
        Returns True if user is responsible for this room. False otherwise.
        """
        if not self.responsibleId:
            return None
        if self.responsibleId == user.id:
            return True
        try:
            if user in self._v_isOwnedBy.keys():
                return self._v_isOwnedBy[user]
        except:
            self._v_isOwnedBy = {}
        if self.customAtts.get( 'Simba List' ):
            list = self.customAtts.get( 'Simba List' )
            if list != "Error: unknown mailing list" and list != "":
                if user.isMemberOfSimbaList( list ):
                    self._v_isOwnedBy[user] = True
                    return True
        self._v_isOwnedBy[user] = False
        return False
    
    def getLocationName( self ):
        if self.__class__.__name__ == 'RoomBase':
            return Location.getDefaultLocation().friendlyName
            #raise 'This method is purely virtual. Call it only on derived objects.'
        return self.getLocationName() # Subclass
    
    def setLocationName( self, locationName ):
        if self.__class__.__name__ == 'RoomBase':
            raise 'This method is purely virtual. Call it only on derived objects.'
        return self.setLocationName( locationName ) # Subclass
    
    def getAccessKey( self ): return ""

    def getFullName( self ):
        name = ""
        if self.building != None and self.floor != None and self.building != None:
            s = str( self.building ) + '-' + str( self.floor ) + '-' + str( self.roomNr )
            if s != '--':
                name = s
        if self._name != None and len( self._name.strip() ) > 0:
            name += " - %s" % self._name
        return name
    
    # ==== Private ===================================================

    _name = None 
    _equipment = ''   # str, 'eq1`eq2`eq3' - list of room's equipment, joined by '`'
    
    def _getGuid( self ):
        if self.id == None or self.locationName == None:
            return None
        return RoomGUID( Location.parse( self.locationName ), self.id )

    def _getName( self ):
        if self._name != None and len( self._name.strip() ) > 0:
            return self._name
        if self.building != None and self.floor != None and self.building != None:
            s = str( self.building ) + '-' + str( self.floor ) + '-' + str( self.roomNr )
            if s != '--':
                return s
            return ''
        return None

    def _setName( self, s ):
        # Try to parse the name
        if s == None:
            self._name = None
            return
        parts = s.split( '-' )
        if len( parts ) == 3:
            try:
                self.building = int( parts[0] )
                self.floor = parts[1]
                self.roomNr = parts[2]
                return 
            except:
                pass
        # Parsing failed, that means it is real name
        self._name = s

    # CERN specific; don't bother
    def _getNeedsAVCSetup( self ):
        eq = self.getEquipment()
        if not self.locationName or not eq:
            return None
        return 'Video conference' in ' '.join( eq )
    
    def _eval_str( self, s ):
        ixPrv = 0
        ret = ""

        while True:
            ix = s.find( "#{", ixPrv )
            if ix == -1:
                break
            ret += s[ixPrv:ix] # verbatim
            ixPrv = s.index( "}", ix + 2 ) + 1
            ret += str( eval( s[ix+2:ixPrv-1] ) )
        ret += s[ixPrv:len(s)]
        
        return ret

    def _getVerboseEquipment( self ):
        s = ""
        eqList = self.getEquipment()
        for eq in eqList:
            s = s + eq + ", "
        if len( eqList ) > 0: s = s[0:len(s)-2] # Cut off last ','
        return s
        
    def _getPhotoId( self ):
        """ 
        Feel free to override this in your inherited class.
        """
        return self._doGetPhotoId()
    
    def _setPhotoId( self, value ):
        self._photoId = value
    
    def _doGetPhotoId( self ):
        if '_photoId' in dir( self ): return self._photoId 
        return None
    
    def __str__( self ):
        s = self._eval_str(
"""
               id: #{self.id}
         isActive: #{self.isActive}

             room: #{self.name}
             
         building: #{self.building}
            floor: #{self.floor}
           roomNr: #{self.roomNr}
     isReservable: #{self.isReservable}
rNeedConfirmation: #{self.resvsNeedConfirmation}

             site: #{self.site}
         capacity: #{self.capacity}
      surfaceArea: #{self.surfaceArea}
         division: #{self.division}
          photoId: #{self.photoId}
       externalId: #{self.externalId}

        telephone: #{self.telephone}
       whereIsKey: #{self.whereIsKey}
         comments: #{self.comments}
    responsibleId: #{self.responsibleId}
        equipment: """
        )
        s += self.verboseEquipment + "\n"
        return s

    def __cmp__( self, other ):
        if self.__class__.__name__ == 'NoneType' and other.__class__.__name__ == 'NoneType':
            return 0
        if self.__class__.__name__ == 'NoneType':
            return cmp( None, 1 )
        if other.__class__.__name__ == 'NoneType':
            return cmp( 1, None )
        
        if self.id != None  and  other.id != None:
            if self.id == other.id:
                return 0
        
        c = cmp( self.locationName, other.locationName )
        if c == 0:
            c = cmp( self.building, other.building )
            if c == 0:
                c = cmp( self.floor, other.floor )
                if c == 0:
                    c = cmp( self.name, other.name )

        return c

    # ==== Properties ===================================================
    
    # DO NOT set default values here, since query-by-example will change!!!

    id = None             # int - artificial ID; initialy value from oracle db
    locationName = property( getLocationName, setLocationName ) # location (plugin) name
    guid = property( _getGuid ) # RoomGUID
    isActive = None       # bool - whether the room is active (not logicaly removed) [STSCRBOK]
    resvsNeedConfirmation = None # bool - whether reservations for this room must be confirmed by responsible

    building = None       # int, positive
    floor = None          # str, alphanumeric
    roomNr = None         # str
    
    name = property( _getName, _setName ) # str - room name
    
    capacity = None       # int, positive
    site = None           # str - global room localisation, i.e. city
    division = None       # str, TODO
    isReservable = None   # bool - whether the room is reservable
    photoId = property( _getPhotoId, _setPhotoId )        # str - room picture id
    externalId = None     # str - custom external room id, i.e. for locating on the map
    
    telephone = None      # str
    surfaceArea = None    # int, positive - in meters^2
    whereIsKey = None     # str, typically telephone number
    comments = None       # str
    responsibleId = None  # str, responsible person id (avatar.id)
    
    #customAtts = {}      # Must behave like name-value dictionary of 
                          # custom attributes. Must be put in derived classes.

    verboseEquipment = property( _getVerboseEquipment )
    needsAVCSetup = property( _getNeedsAVCSetup )