def test_pre_post_fill(self): """ Tests whether the filling algorithm correctly fills a typical timeslot query in which both ends of the range require pre or post-filling. """ hour = timedelta(hours=1) filled = filler.fill( self.timeslots, self.timeslots[0].range_start() - hour, self.timeslots[-1].range_end() + hour ) self.general_fill_tests(filled) self.assertNotEqual( self.timeslots[0], filled[0], 'Filler mistakenly did not fill before first show.' ) self.assertNotEqual( self.timeslots[-1], filled[-1], 'Filler mistakenly did not fill after first show.' )
def test_normal_fill(self): """ Tests whether the filling algorithm correctly fills a typical timeslot query in which neither end of the range requires pre or post-filling. """ filled = filler.fill( self.timeslots, self.timeslots[0].range_start(), self.timeslots[-1].range_end() ) self.general_fill_tests(filled) self.assertIs( self.timeslots[0], filled[0], 'Filler mistakenly filled before first show.' ) self.assertIs( self.timeslots[-1], filled[-1], 'Filler mistakenly filled after first show.' )
def test_normal_fill(self): """ Tests whether an attempt to fill an empty list returns a single filler slot spanning the entire requested range. """ # First, some sanity checks on the test fixture self.assertIsNotNone(Term.of(self.past_time)) self.assertIsNotNone(self.past_time) self.assertIsNotNone(self.future_time) self.assertIsNotNone(self.duration) filled = filler.fill( self.timeslots, self.past_time, self.future_time ) # This should only have filled with one item self.assertEqual(len(filled), 1) filler_slot = filled[0] # ...which should be a Timeslot... self.assertIsInstance(filler_slot, Timeslot) # ... and should take up the entire required range. # The times might be out because filler slots try to set # their start/ends to end/starts of adjacent shows. self.assertTrue(filler_slot.start_time <= self.past_time) self.assertTrue(filler_slot.end_time >= self.future_time)
def test_negative_fill(self): """ Tests whether an attempt to fill an empty list whose start time is after its end time results in an exception. """ # First, some sanity checks on the test fixture self.assertIsNotNone(Term.of(self.past_time)) self.assertIsNotNone(self.past_time) self.assertIsNotNone(self.future_time) self.assertIsNotNone(self.duration) with self.assertRaises(ValueError): filler.fill( self.timeslots, self.future_time, self.past_time )
def test_normal_fill(self): """Tests whether an attempt to fill an empty list returns a single filler slot spanning the entire requested range. """ filled = filler.fill( self.timeslots, self.past_time, self.future_time ) self.AssertTrue(len(filled) == 1) filler_slot = filled[0] self.assertTrue(isinstance(filler_slot, Timeslot)) self.assertEqual(filler_slot.start_time, self.past_time) self.assertEqual(filler_slot.duration, self.duration)
def coming_up(date=None, quantity=10, with_filler_timeslots=True): """Retrieves the next 'quantity' timeslots, relative to 'date'. If there are not enough timeslots to make up 'quantity', the resulting list may be smaller. If 'with_filler_timeslots' is True, these are included in the total. Keyword arguments: date -- the reference point from which the list is made; the first listed timeslot is the timeslot active at the moment of time 'date' refers to (default: now) quantity -- the maximum amount of slots to retrieve, as a positive integer (default: 10) with_filler_timeslots -- if True, non-contiguous entries in the list will cause the resulting gap to be filled in with a filler timeslot; filler timeslots count towards 'quantity' (default: True) """ if quantity <= 0: raise ValueError("'quantity' must be positive.") if date is None: date = timezone.now() query = Timeslot.objects.filter( duration__gte=date - F('start_time')).order_by('start_time') coming_up_unfilled = \ list(query)[:min(query.count(), quantity)] \ if query.exists() else [] if with_filler_timeslots: end = date if not coming_up_unfilled else \ coming_up_unfilled[-1] filled_up = filler.fill( coming_up_unfilled, date, end) # Filling might have added some more slots, so we need to # re-trim coming_up = filled_up[:min(len(filled_up), quantity)] else: coming_up = coming_up_unfilled return coming_up
def between(cls, start, end, exclude_before_start=False, exclude_after_end=False, exclude_subsuming=False, with_filler_timeslots=True): """Returns all the timeslots within a range defined by two datetime objects. Keyword arguments: start -- the start of the range, as a datetime end -- the end of the range, as a datetime exclude_before_start -- if True, the list will exclude all shows that start before the range, but end within it (default: False) exclude_after_end -- if True, the list will exclude all shows that start within the range, but end after it (default: False) exclude_subsuming -- if True, the list will exclude all shows that start before, but end after, the range (that is, they "subsume" the range) (default: False) with_filler_timeslots -- if True, gaps within the range will be filled with references to the filler pseudo-show (default: True) """ # THIS IS NOT A TRIVIAL FUNCTION! # Start with ALL the timeslots (Django doesn't execute # database queries immediately so this is perfectly fine, # we'll be whittling this query down soon! timeslots = Timeslot.objects.all() # ADVICE: Whenever you see an inequality on duration, just # mentally move the subtraction of 'start_time' over to # an addition on the other end, and replace duration + # start_time with end_time. That should make sense hopefully # (this is because the model doesn't store end times in the # database) # Get rid of shows that start and end before the range # (diagrammatically, ##| | ) timeslots = timeslots.exclude( start_time__lt=start, duration__lte=start - F('start_time') ) # And start and end after the range # (diagrammatically, | |##) timeslots = timeslots.exclude( start_time__gte=end, duration__gt=end - F('start_time') ) # This leaves: # 1) Shows that start and end inside the range # - these will always be returned # (diagrammatically, |##| ) # 2) Shows that start before but end inside the range # - these will be returned if exclude_before_start=False # (diagrammatically, ##|##| ) # 3) Shows that start inside but end after the range # - these will be returned if exclude_after_end=False # (diagrammatically, |##|##) # 4) Shows that completely subsume the range # - these will be returned if exclude_subsuming=False # (diagrammatically, ##|##|##) if exclude_before_start: # 1) timeslots = timeslots.exclude( start_time__lt=start, duration__lte=end - F('start_time')) if exclude_after_end: # 2) timeslots = timeslots.exclude( start_time__gte=start, duration__gt=end - F('start_time')) if exclude_subsuming: # 3) timeslots = timeslots.exclude( start_time__lt=start, duration__gt=end - F('start_time')) # Of course, we want some form of ordering timeslots = timeslots.order_by("start_time") # And jukebox filling if with_filler_timeslots: # Can't do add_filler_timeslots if this is a queryset # so force it to be a list timeslots = filler.fill( list(timeslots), start, end) # For all intents and purposes, this is just the queryset # with some added metadata on which range it actually # represents return cls( timeslots, start, end, exclude_before_start, exclude_after_end, exclude_subsuming, with_filler_timeslots)