# HG changeset patch # User Paul Boddie # Date 1445259122 -7200 # Node ID 1c91c04bcfcb8cbe015b017edd421fa974acc9fe # Parent 8a9d331c6b8b66aa021a2d75735cf5a533495f4f Permit the removal of exceptions when adding recurrences. Introduced improved functions for obtaining recurrence periods from objects. diff -r 8a9d331c6b8b -r 1c91c04bcfcb imiptools/data.py --- a/imiptools/data.py Sun Oct 18 23:58:02 2015 +0200 +++ b/imiptools/data.py Mon Oct 19 14:52:02 2015 +0200 @@ -129,6 +129,9 @@ def get_date_value_items(self, name, tzid=None): return get_date_value_items(self.details, name, tzid) + def get_date_value_item_periods(self, name, tzid=None): + return get_date_value_item_periods(self.details, name, self.get_main_period(tzid).get_duration(), tzid) + def get_period_values(self, name, tzid=None): return get_period_values(self.details, name, tzid) @@ -475,24 +478,27 @@ self["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})] return sequence - def update_exceptions(self, excluded): + def update_exceptions(self, excluded, asserted): """ Update the exceptions to any rule by applying the list of 'excluded' - periods. + periods. Where 'asserted' periods are provided, exceptions will be + removed corresponding to those periods. """ - to_exclude = set(excluded).difference(self.get_date_values("EXDATE") or []) - if not to_exclude: - return False + old_exdates = self.get_date_value_item_periods("EXDATE") + new_exdates = set(old_exdates) + new_exdates.update(excluded) + new_exdates.difference_update(asserted) - if not self.has_key("EXDATE"): + if not new_exdates: + del self["EXDATE"] + else: self["EXDATE"] = [] + for p in new_exdates: + self["EXDATE"].append(get_period_item(p.get_start(), p.get_end())) - for p in to_exclude: - self["EXDATE"].append(get_period_item(p.get_start(), p.get_end())) - - return True + return set(old_exdates) != new_exdates def correct_object(self, tzid, permitted_values): @@ -720,6 +726,29 @@ else: return None +def get_date_value_item_periods(d, name, duration, tzid=None): + + """ + Obtain items from 'd' having the given 'name', where a single item yields + potentially many values. The 'duration' must be provided to define the + length of periods having only a start datetime. Return a list of periods + corresponding to the property in 'd'. + """ + + items = get_date_value_items(d, name, tzid) + if not items: + return items + + periods = [] + + for value, attr in items: + if isinstance(value, tuple): + periods.append(RecurringPeriod(value[0], value[1], tzid, name, attr)) + else: + periods.append(RecurringPeriod(value, value + duration, tzid, name, attr)) + + return periods + def get_period_values(d, name, tzid=None): """ @@ -896,10 +925,6 @@ dtstart = main_period.get_start() dtstart_attr = main_period.get_start_attr() - dtend = main_period.get_end() - dtend_attr = main_period.get_end_attr() - - duration = dtend - dtstart # Attempt to get time zone details from the object, using the supplied zone # only as a fallback. @@ -928,7 +953,7 @@ for recurrence_start in selector.materialise(dtstart, end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive): create = len(recurrence_start) == 3 and date or datetime recurrence_start = to_timezone(create(*recurrence_start), obj_tzid) - recurrence_end = recurrence_start + duration + recurrence_end = recurrence_start + main_period.get_duration() periods.append(RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr)) else: @@ -936,14 +961,9 @@ # Add recurrence dates. - rdates = obj.get_date_value_items("RDATE", tzid) - + rdates = obj.get_date_value_item_periods("RDATE", tzid) if rdates: - for rdate, rdate_attr in rdates: - if isinstance(rdate, tuple): - periods.append(RecurringPeriod(rdate[0], rdate[1], tzid, "RDATE", rdate_attr)) - else: - periods.append(RecurringPeriod(rdate, rdate + duration, tzid, "RDATE", rdate_attr)) + periods += rdates # Return a sorted list of the periods. @@ -951,14 +971,10 @@ # Exclude exception dates. - exdates = obj.get_date_value_items("EXDATE", tzid) + exdates = obj.get_date_value_item_periods("EXDATE", tzid) if exdates: - for exdate, exdate_attr in exdates: - if isinstance(exdate, tuple): - period = RecurringPeriod(exdate[0], exdate[1], tzid, "EXDATE", exdate_attr) - else: - period = RecurringPeriod(exdate, exdate + duration, tzid, "EXDATE", exdate_attr) + for period in exdates: i = bisect_left(periods, period) while i < len(periods) and periods[i] == period: del periods[i] diff -r 8a9d331c6b8b -r 1c91c04bcfcb imipweb/event.py --- a/imipweb/event.py Sun Oct 18 23:58:02 2015 +0200 +++ b/imipweb/event.py Mon Oct 19 14:52:02 2015 +0200 @@ -830,14 +830,18 @@ to_unschedule, to_exclude = self.get_removed_periods(periods) periods = set(periods) + active_periods = [p for p in periods if not p.replaced] changed = self.obj.set_period(period) or changed changed = self.obj.set_periods(periods) or changed - changed = self.obj.update_exceptions(to_exclude) or changed + + # Add and remove exceptions. + + changed = self.obj.update_exceptions(to_exclude, active_periods) or changed # Assert periods restored after cancellation. - changed = self.revert_cancellations([p for p in periods if not p.replaced]) or changed + changed = self.revert_cancellations(active_periods) or changed # Organiser-only changes...