[docs]classAvailability(PretalxModel):"""The Availability class models when people or rooms are available for. :class:`~pretalx.schedule.models.slot.TalkSlot` objects. The power of this class is not within its rather simple data model, but with the operations available on it. An availability object can span multiple days, but due to our choice of input widget, it will usually only span a single day at most. """event=models.ForeignKey(to="event.Event",related_name="availabilities",on_delete=models.CASCADE)person=models.ForeignKey(to="person.SpeakerProfile",related_name="availabilities",on_delete=models.CASCADE,null=True,blank=True,)room=models.ForeignKey(to="schedule.Room",related_name="availabilities",on_delete=models.CASCADE,null=True,blank=True,)start=models.DateTimeField()end=models.DateTimeField()def__str__(self)->str:person=self.person.user.get_display_name()ifself.personelseNoneroom=getattr(self.room,"name",None)event=getattr(getattr(self,"event",None),"slug",None)returnf"Availability(event={event}, person={person}, room={room})"def__hash__(self):returnhash((self.person,self.room,self.start,self.end))
[docs]def__eq__(self,other:"Availability")->bool:"""Comparisons like ``availability1 == availability2``. Checks if ``event``, ``person``, ``room``, ``start`` and ``end`` are the same. """returnall(getattr(self,attribute,None)==getattr(other,attribute,None)forattributein("person","room","start","end"))
@cached_propertydefall_day(self)->bool:"""Checks if the Availability spans one (or, technically: multiple) complete day."""returnself.start.time()==zerotimeandself.end.time()==zerotimedefserialize(self)->dict:frompretalx.api.serializers.roomimportAvailabilitySerializerreturnAvailabilitySerializer(self).data
[docs]defoverlaps(self,other:"Availability",strict:bool)->bool:"""Test if two Availabilities overlap. :param strict: Only count a real overlap as overlap, not direct adjacency. """ifnotisinstance(other,Availability):raiseException("Please provide an Availability object")ifstrict:return((self.start<=other.start<self.end)or(self.start<other.end<=self.end)or(other.start<=self.start<other.end)or(other.start<self.end<=other.end))return((self.start<=other.start<=self.end)or(self.start<=other.end<=self.end)or(other.start<=self.start<=other.end)or(other.start<=self.end<=other.end))
[docs]defcontains(self,other:"Availability")->bool:"""Tests if this availability starts before and ends after the other."""returnself.start<=other.startandself.end>=other.end
[docs]defmerge_with(self,other:"Availability")->"Availability":"""Return a new Availability which spans the range of this one and the given one."""ifnotisinstance(other,Availability):raiseException("Please provide an Availability object.")ifnotother.overlaps(self,strict=False):raiseException("Only overlapping Availabilities can be merged.")returnAvailability(start=min(self.start,other.start),end=max(self.end,other.end),event=getattr(self,"event",None),person=getattr(self,"person",None),room=getattr(self,"room",None),)
[docs]def__or__(self,other:"Availability")->"Availability":"""Performs the merge operation: ``availability1 | availability2``"""returnself.merge_with(other)
[docs]defintersect_with(self,other:"Availability")->"Availability":"""Return a new Availability which spans the range covered both by this one and the given one."""ifnotisinstance(other,Availability):raiseException("Please provide an Availability object.")ifnotother.overlaps(self,False):raiseException("Only overlapping Availabilities can be intersected.")returnAvailability(start=max(self.start,other.start),end=min(self.end,other.end),event=getattr(self,"event",None),person=getattr(self,"person",None),room=getattr(self,"room",None),)
[docs]def__and__(self,other:"Availability")->"Availability":"""Performs the intersect operation: ``availability1 & availability2``"""returnself.intersect_with(other)
[docs]@classmethoddefunion(cls,availabilities:list["Availability"])->list["Availability"]:"""Return the minimal list of Availability objects which are covered by at least one given Availability."""availabilities=list(availabilities)ifnotavailabilities:return[]availabilities=sorted(availabilities,key=lambdaavail:avail.start)result=[availabilities[0]]availabilities=availabilities[1:]foravailinavailabilities:ifavail.overlaps(result[-1],False):result[-1]=result[-1].merge_with(avail)else:result.append(avail)returnresult
@classmethoddef_pair_intersection(cls,availabilities_a:list["Availability"],availabilities_b:list["Availability"],)->list["Availability"]:"""return the list of Availabilities, which are covered by each of the given sets."""result=[]# yay for O(b*a) time! I am sure there is some fancy trick to make this faster,# but we're dealing with less than 100 items in total, sooo.. ¯\_(ツ)_/¯foravail_ainavailabilities_a:foravail_binavailabilities_b:ifavail_a.overlaps(avail_b,True):result.append(avail_a.intersect_with(avail_b))returnresult
[docs]@classmethoddefintersection(cls,*availabilitysets:list["Availability"])->list["Availability"]:"""Return the list of Availabilities which are covered by all of the given sets."""# get rid of any overlaps and unmerged ranges in each setavailabilitysets=[cls.union(avialset)foravialsetinavailabilitysets]# bail out for obvious cases (there are no sets given, one of the sets is empty)ifnotavailabilitysets:return[]ifnotall(availabilitysets):return[]# start with the very first set ...result=availabilitysets[0]foravailsetinavailabilitysets[1:]:# ... subtract each of the other setsresult=cls._pair_intersection(result,availset)returnresult