def test_get_occurrence(self): # self.occ is a persisted Occurrence occ_replacer = OccurrenceReplacer([self.occ]) res = occ_replacer.get_occurrence(self.occ) assert res == self.occ res = occ_replacer.get_occurrence(self.occ) assert res == self.occ
def test_get_occurrence(self): # self.occ is a persisted Occurrence occ_replacer = OccurrenceReplacer([self.occ]) res = occ_replacer.get_occurrence(self.occ) self.assertEqual(res, self.occ) res = occ_replacer.get_occurrence(self.occ) self.assertEqual(res, self.occ)
def get_occurrences(self, start, end): """ >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00 to 2008-02-02 00:00:00', '2008-03-01 00:00:00 to 2008-03-02 00:00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] """ persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence( occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else:
def occurrences_after(self, after=None): """ returns a generator that produces occurrences after the datetime ``after``. Includes all of the persisted Occurrences. """ occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator(after) while True: next = generator.next() yield occ_replacer.get_occurrence(next)
def test_get_occurrence_works_for_event_like_object(self): # get_occurrence method checks the duck expected_ok = False occ_replacer = OccurrenceReplacer([self.occ]) try: occ_replacer.get_occurrence(int) except AttributeError: expected_ok = True assert expected_ok
def test_get_additional_occurrences(self): occ_replacer = OccurrenceReplacer([self.occ]) other_occ = Occurrence(event=self.event2, start=self.start + datetime.timedelta(days=5), end=self.end, original_start=self.start, original_end=self.end) other_occ.save() res = occ_replacer.get_additional_occurrences(self.start, self.end) assert [self.occ] == res
def test_has_occurrence_with_other_event(self): other_occ = Occurrence.objects.create(event=self.event2, start=self.start, end=self.end, original_start=self.start, original_end=self.end) occ_replacer = OccurrenceReplacer([self.occ]) self.assertTrue(occ_replacer.has_occurrence(self.occ)) self.assertFalse(occ_replacer.has_occurrence(other_occ))
def test_has_occurrence_with_other_event(self): other_occ = Occurrence.objects.create( event=self.event2, start=self.start, end=self.end, original_start=self.start, original_end=self.end) occ_replacer = OccurrenceReplacer([self.occ]) self.assertTrue(occ_replacer.has_occurrence(self.occ)) self.assertFalse(occ_replacer.has_occurrence(other_occ))
def test_get_additional_occurrences(self): occ_replacer = OccurrenceReplacer([self.occ]) # Other occurrence. Occurrence.objects.create(event=self.event2, start=self.start + datetime.timedelta(days=5), end=self.end, original_start=self.start, original_end=self.end) res = occ_replacer.get_additional_occurrences(self.start, self.end) self.assertEqual(res, [self.occ])
def test_get_additional_occurrences(self): occ_replacer = OccurrenceReplacer([self.occ]) # Other occurrence. Occurrence.objects.create( event=self.event2, start=self.start + datetime.timedelta(days=5), end=self.end, original_start=self.start, original_end=self.end) res = occ_replacer.get_additional_occurrences(self.start, self.end) self.assertEqual(res, [self.occ])
def occurrences_after(self, after=None, max_occurences=None): """ returns a generator that produces occurrences after the datetime ``after``. Includes all of the persisted Occurrences. (Optionally) This generator will return up to ``max_occurences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest. """ occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator(after, max_occurences=max_occurences) while True: next_occurence = next(generator) yield occ_replacer.get_occurrence(next_occurence)
def test_has_occurrence_with_other_event(self): other_occ = Occurrence(event=self.event2, start=self.start, end=self.end, original_start=self.start, original_end=self.end) other_occ.save() occ_replacer = OccurrenceReplacer([self.occ]) assert occ_replacer.has_occurrence(self.occ) assert not occ_replacer.has_occurrence(other_occ)
def occurrences_after(self, after=None, max_occurences=None): """ returns a generator that produces occurrences after the datetime ``after``. Includes all of the persisted Occurrences. (Optionally) This generator will return up to ``max_occurences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest. """ occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator( after, max_occurences=max_occurences) while True: next_occurence = next(generator) yield occ_replacer.get_occurrence(next_occurence)
def get_occurrences(self, start, end, skip_booster=False, persisted_occurrences=None): """ :param persisted_occurrences - In some contexts (such as models post_constraints), we need to ensure that we get the latest set of persisted_occurrences and avoid using the prefetch cache which may be stale. Client code can pass its own persisted_occurrences using the `all().all()` pattern in these cases. >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] """ if self.pk and not skip_booster: # performance booster for occurrences relationship Event.objects.prefetch_related('occurrence_set').get(pk=self.pk) if persisted_occurrences is None: persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start <= end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences(start, end) return final_occurrences
def occurrences_after(self, after=None, max_occurences=None): """ returns a generator that produces occurrences after the datetime ``after``. Includes all of the persisted Occurrences. (Optionally) This generator will return up to ``max_occurences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest. """ if after is None: after = timezone.now() occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator(after) trickies = list(self.occurrence_set.filter(original_start__lte=after, start__gte=after).order_by('start')) for index, nxt in enumerate(generator): if max_occurences and index > max_occurences - 1: break if (len(trickies) > 0 and (nxt is None or nxt.start > trickies[0].start)): yield trickies.pop(0) yield occ_replacer.get_occurrence(nxt)
def occurrences_after(self, after=None): """ returns a generator that produces occurrences after the datetime ``after``. Includes all of the persisted Occurrences. """ if after is None: after = timezone.now() occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator(after) trickies = list(self.occurrence_set.filter(original_start__lte=after, start__gte=after).order_by('start')) while True: try: nxt = next(generator) except StopIteration: nxt = None if (len(trickies) > 0 and (nxt is None or nxt.start > trickies[0].start)): yield trickies.pop(0) if (nxt is None): raise StopIteration yield occ_replacer.get_occurrence(nxt)
def occurrences_after(self, after=None): """ returns a generator that produces occurences after the datetime ``after``. Includes all of the persisted Occurrences. """ if after is None: after = timezone.now() occ_replacer = OccurrenceReplacer(self.occurrence_set.all()) generator = self._occurrences_after_generator(after) trickies = list(self.occurrence_set.filter(original_start__lte=after, start__gte=after).order_by('start')) while True: try: nxt = next(generator) except StopIteration: nxt = None if (len(trickies) > 0 and (nxt is None or nxt.start > trickies[0].start)): yield trickies.pop(0) if (nxt is None): raise StopIteration yield occ_replacer.get_occurrence(nxt)
def get_occurrences(self, start, end): """ >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] ` """ if self.pk: # performance booster for occurrences relationship Event.objects.select_related('occurrence').get(pk=self.pk) persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences( start, end) return final_occurrences
def get_occurrences(self, start, end): """ >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] ` """ if self.pk: # performance booster for occurrences relationship Event.objects.select_related('occurrence').get(pk=self.pk) persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences(start, end) return final_occurrences
def get_occurrences(self, start, end): if self.pk: # performance booster for occurrences relationship Event.objects.select_related('occurrence').get(pk=self.pk) persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences( start, end) return final_occurrences
def test_get_occurrence_works_for_event_like_object(self): # get_occurrence method checks the duck occ_replacer = OccurrenceReplacer([self.occ]) with self.assertRaises(AttributeError): occ_replacer.get_occurrence(int)
def get_occurrences(self, start, end, clear_prefetch=True): """ >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] """ # Explanation of clear_prefetch: # # Periods, and their subclasses like Week, call # prefetch_related('occurrence_set') on all events in their # purview. This reduces the database queries they make from # len()+1 to 2. However, having a cached occurrence_set on the # Event model instance can sometimes cause Events to have a # different view of the state of occurrences than the Period # managing them. # # E.g., if you create an unsaved occurrence, move it to a # different time [which saves the event], keep a reference to # the moved occurrence, & refetch all occurrences from the # Period without clearing the prefetch cache, you'll end up # with two Occurrences for the same event but different moved # states. It's a complicated scenario, but can happen. (See # tests/test_occurrence.py#test_moved_occurrences, which caught # this bug in the first place.) # # To prevent this, we clear the select_related cache by default # before we call an event's get_occurrences, but allow Period # to override this cache clear since it already fetches all # occurrence_sets via prefetch_related in its get_occurrences. if clear_prefetch: persisted_occurrences = self.occurrence_set.select_related( None).all() else: persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences( start, end) return final_occurrences
def get_occurrences(self, start, end, clear_prefetch=True): """ >>> rule = Rule(frequency = "MONTHLY", name = "Monthly") >>> rule.save() >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2)) >>> event.rule <Rule: Monthly> >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00'] Ensure that if an event has no rule, that it appears only once. >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0)) >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2)) >>> ["%s to %s" %(o.start, o.end) for o in occurrences] [] """ # Explanation of clear_prefetch: # # Periods, and their subclasses like Week, call # prefetch_related('occurrence_set') on all events in their # purview. This reduces the database queries they make from # len()+1 to 2. However, having a cached occurrence_set on the # Event model instance can sometimes cause Events to have a # different view of the state of occurrences than the Period # managing them. # # E.g., if you create an unsaved occurrence, move it to a # different time [which saves the event], keep a reference to # the moved occurrence, & refetch all occurrences from the # Period without clearing the prefetch cache, you'll end up # with two Occurrences for the same event but different moved # states. It's a complicated scenario, but can happen. (See # tests/test_occurrence.py#test_moved_occurrences, which caught # this bug in the first place.) # # To prevent this, we clear the select_related cache by default # before we call an event's get_occurrences, but allow Period # to override this cache clear since it already fetches all # occurrence_sets via prefetch_related in its get_occurrences. if clear_prefetch: persisted_occurrences = self.occurrence_set.select_related(None).all() else: persisted_occurrences = self.occurrence_set.all() occ_replacer = OccurrenceReplacer(persisted_occurrences) occurrences = self._get_occurrence_list(start, end) final_occurrences = [] for occ in occurrences: # replace occurrences with their persisted counterparts if occ_replacer.has_occurrence(occ): p_occ = occ_replacer.get_occurrence(occ) # ...but only if they are within this period if p_occ.start < end and p_occ.end >= start: final_occurrences.append(p_occ) else: final_occurrences.append(occ) # then add persisted occurrences which originated outside of this period but now # fall within it final_occurrences += occ_replacer.get_additional_occurrences(start, end) return final_occurrences
def test_get_additional_occurrences_cancelled(self): occ_replacer = OccurrenceReplacer([self.occ]) self.occ.cancelled = True self.occ.save() res = occ_replacer.get_additional_occurrences(self.start, self.end) self.assertEqual(res, [])