# HG changeset patch # User Paul Boddie # Date 1438188071 -7200 # Node ID b021078b64d4aae77b30480ddc4f89c0d073d961 # Parent 55518c07a9ff4e9ead141e50b9226bd3e95f26ab Tidied up somewhat, moving update_attendees into the client abstraction and adjusting methods in the manager's event module. diff -r 55518c07a9ff -r b021078b64d4 imiptools/client.py --- a/imiptools/client.py Wed Jul 29 16:40:28 2015 +0200 +++ b/imiptools/client.py Wed Jul 29 18:41:11 2015 +0200 @@ -32,49 +32,6 @@ from imiptools.profile import Preferences import imip_store -def update_attendees(obj, attendees, removed): - - """ - Update the attendees in 'obj' with the given 'attendees' and 'removed' - attendee lists. A list is returned containing the attendees whose - attendance should be cancelled. - """ - - to_cancel = [] - - existing_attendees = uri_values(obj.get_values("ATTENDEE") or []) - added = set(attendees).difference(existing_attendees) - - if added or removed: - attendees = uri_items(obj.get_items("ATTENDEE") or []) - sequence = obj.get_value("SEQUENCE") - - if removed: - remaining = [] - - for attendee, attendee_attr in attendees: - if attendee in removed: - - # Without a sequence number, assume that the event has not - # been published and that attendees can be silently removed. - - if sequence is not None: - to_cancel.append((attendee, attendee_attr)) - else: - remaining.append((attendee, attendee_attr)) - - attendees = remaining - - if added: - for attendee in added: - attendee = attendee.strip() - if attendee: - attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"})) - - obj["ATTENDEE"] = attendees - - return to_cancel - class Client: "Common handler and manager methods." @@ -126,6 +83,49 @@ # Common operations on calendar data. + def update_attendees(self, obj, attendees, removed): + + """ + Update the attendees in 'obj' with the given 'attendees' and 'removed' + attendee lists. A list is returned containing the attendees whose + attendance should be cancelled. + """ + + to_cancel = [] + + existing_attendees = uri_values(obj.get_values("ATTENDEE") or []) + added = set(attendees).difference(existing_attendees) + + if added or removed: + attendees = uri_items(obj.get_items("ATTENDEE") or []) + sequence = obj.get_value("SEQUENCE") + + if removed: + remaining = [] + + for attendee, attendee_attr in attendees: + if attendee in removed: + + # Without a sequence number, assume that the event has not + # been published and that attendees can be silently removed. + + if sequence is not None: + to_cancel.append((attendee, attendee_attr)) + else: + remaining.append((attendee, attendee_attr)) + + attendees = remaining + + if added: + for attendee in added: + attendee = attendee.strip() + if attendee: + attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"})) + + obj["ATTENDEE"] = attendees + + return to_cancel + def update_participation(self, obj, partstat=None): """ diff -r 55518c07a9ff -r b021078b64d4 imiptools/data.py --- a/imiptools/data.py Wed Jul 29 16:40:28 2015 +0200 +++ b/imiptools/data.py Wed Jul 29 18:41:11 2015 +0200 @@ -149,6 +149,15 @@ dtend, dtend_attr = self.get_datetime_item("DTEND") return get_tzid(dtstart_attr, dtend_attr) + def is_shared(self): + + """ + Return whether this object is shared based on the presence of a SEQUENCE + property. + """ + + return self.get_value("SEQUENCE") is not None + # Construction and serialisation. def make_calendar(nodes, method=None): diff -r 55518c07a9ff -r b021078b64d4 imipweb/event.py --- a/imipweb/event.py Wed Jul 29 16:40:28 2015 +0200 +++ b/imipweb/event.py Wed Jul 29 18:41:11 2015 +0200 @@ -20,7 +20,6 @@ """ from datetime import date, timedelta -from imiptools.client import update_attendees from imiptools.data import get_uri, uri_dict, uri_values from imiptools.dates import format_datetime, get_datetime_item, \ get_period_item, to_date, to_timezone @@ -60,11 +59,51 @@ (None, "Not indicated"), ] + # Access to stored object information. + def is_organiser(self, obj): return get_uri(obj.get_value("ORGANIZER")) == self.user + def get_stored_attendees(self, obj): + return uri_values(obj.get_values("ATTENDEE") or []) + + def get_stored_main_period(self, obj): + + """ + Return the main event period for the given 'obj'. + """ + + dtstart, dtstart_attr = obj.get_datetime_item("DTSTART") + + if obj.has_key("DTEND"): + dtend, dtend_attr = obj.get_datetime_item("DTEND") + elif obj.has_key("DURATION"): + duration = obj.get_duration("DURATION") + dtend = dtstart + duration + dtend_attr = dtstart_attr + else: + dtend, dtend_attr = dtstart, dtstart_attr + + return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr) + + def get_stored_recurrences(self, obj): + + "Return recurrences computed using the given 'obj'." + + recurrences = [] + for period in self.get_periods(obj): + if period.origin != "DTSTART": + recurrences.append(period) + return recurrences + # Request logic methods. + def is_initial_load(self): + + "Return whether the event is being loaded and shown for the first time." + + return not self.env.get_args().has_key("editing") + def handle_request(self, obj): """ @@ -124,9 +163,7 @@ # Set the periods in the object, first obtaining removed and # modified period information. - to_unschedule = [] - for i in args.get("recur-remove", []): - to_unschedule.append(periods[int(i)]) + to_unschedule = self.get_removed_periods() self.set_period_in_object(obj, period) self.set_periods_in_object(obj, periods) @@ -138,9 +175,9 @@ # Obtain any participants and those to be removed. - attendees = self.get_attendees() + attendees = self.get_attendees_from_page() removed = [attendees[int(i)] for i in args.get("remove", [])] - to_cancel = update_attendees(obj, attendees, removed) + to_cancel = self.update_attendees(obj, attendees, removed) single_user = not attendees or attendees == [self.user] # Update attendee participation for the current user. @@ -291,29 +328,16 @@ return all_values def get_current_main_period(self, obj): - if self.is_initial_load() or not self.is_organiser(obj): - return self.get_existing_main_period(obj) - else: - return self.get_main_period() - - def get_existing_main_period(self, obj): """ - Return the main event period for the given 'obj'. + Return the currently active main period for 'obj' depending on whether + editing has begun or whether the object has just been loaded. """ - dtstart, dtstart_attr = obj.get_datetime_item("DTSTART") - - if obj.has_key("DTEND"): - dtend, dtend_attr = obj.get_datetime_item("DTEND") - elif obj.has_key("DURATION"): - duration = obj.get_duration("DURATION") - dtend = dtstart + duration - dtend_attr = dtstart_attr + if self.is_initial_load() or not self.is_organiser(obj): + return self.get_stored_main_period(obj) else: - dtend, dtend_attr = dtstart, dtstart_attr - - return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr) + return self.get_main_period() def get_main_period(self): @@ -336,20 +360,10 @@ """ if self.is_initial_load() or not self.is_organiser(obj): - return self.get_existing_recurrences(obj) + return self.get_stored_recurrences(obj) else: return self.get_recurrences() - def get_existing_recurrences(self, obj): - - "Return recurrences computed using the given 'obj'." - - recurrences = [] - for period in self.get_periods(obj): - if period.origin != "DTSTART": - recurrences.append(period) - return recurrences - def get_recurrences(self): "Return the recurrences defined in the event form." @@ -374,11 +388,15 @@ return periods - def is_initial_load(self): + def get_removed_periods(self): + + "Return a list of recurrence periods to remove upon updating an event." - "Return whether the event is being loaded and shown for the first time." - - return not self.env.get_args().has_key("editing") + to_unschedule = [] + args = self.env.get_args() + for i in args.get("recur-remove", []): + to_unschedule.append(periods[int(i)]) + return to_unschedule def get_current_attendees(self, obj): @@ -388,14 +406,11 @@ """ if self.is_initial_load() or not self.is_organiser(obj): - return self.get_existing_attendees(obj) + return self.get_stored_attendees(obj) else: - return self.get_attendees() + return self.get_attendees_from_page() - def get_existing_attendees(self, obj): - return uri_values(obj.get_values("ATTENDEE") or []) - - def get_attendees(self): + def get_attendees_from_page(self): """ Return attendees from the request, normalised for iCalendar purposes, @@ -418,15 +433,14 @@ return ordered_attendees - def update_attendees(self, obj): + def update_attendees_from_page(self, obj): "Add or remove attendees. This does not affect the stored object." args = self.env.get_args() - attendees = self.get_attendees() - existing_attendees = self.get_existing_attendees(obj) - sequence = obj.get_value("SEQUENCE") + attendees = self.get_attendees_from_page() + existing_attendees = self.get_stored_attendees(obj) if args.has_key("add"): attendees.append("") @@ -443,7 +457,7 @@ existing = attendee in existing_attendees - if not existing or sequence is None or attendee == self.user: + if not existing or not obj.is_shared() or attendee == self.user: attendees.remove(attendee) return attendees @@ -460,7 +474,6 @@ attendees = self.get_current_attendees(obj) is_attendee = self.user in attendees is_request = (obj.get_uid(), obj.get_recurrenceid()) in self._get_requests() - sequence = obj.get_value("SEQUENCE") # Show appropriate options depending on the role of the user. @@ -479,10 +492,10 @@ page.p("As organiser, you can perform the following:") page.p() - self._control("create", "submit", sequence is None and "Create event" or "Update event") + self._control("create", "submit", not obj.is_shared() and "Create event" or "Update event") page.add(" ") - if sequence is not None and not is_request: + if obj.is_shared() and not is_request: self._control("cancel", "submit", "Cancel event") else: self._control("discard", "submit", "Discard event") @@ -512,9 +525,9 @@ # Obtain basic event information, generating any necessary editing controls. if self.is_initial_load() or not self.is_organiser(obj): - attendees = self.get_existing_attendees(obj) + attendees = self.get_stored_attendees(obj) else: - attendees = self.update_attendees(obj) + attendees = self.update_attendees_from_page(obj) p = self.get_current_main_period(obj) self.show_object_datetime_controls(p) @@ -650,13 +663,12 @@ existing = attendee_attr is not None partstat = attendee_attr and attendee_attr.get("PARTSTAT") - sequence = obj.get_value("SEQUENCE") page.td(class_="objectvalue") # Show a form control as organiser for new attendees. - if self.is_organiser(obj) and (not existing or sequence is None): + if self.is_organiser(obj) and (not existing or not obj.is_shared()): self._control("attendee", "value", attendee, size="40") else: self._control("attendee", "hidden", attendee) @@ -684,7 +696,7 @@ # Permit the removal of newly-added attendees. - remove_type = (not existing or sequence is None or attendee == self.user) and "submit" or "checkbox" + remove_type = (not existing or not obj.is_shared() or attendee == self.user) and "submit" or "checkbox" self._control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove") page.label("Remove", for_="remove-%d" % i, class_="remove") @@ -773,8 +785,6 @@ page = self.page args = self.env.get_args() - sequence = obj.get_value("SEQUENCE") - p = event_period_from_period(period) replaced = not recurrenceid and self.is_replaced(p, recurrenceids) @@ -806,7 +816,7 @@ page.th("") page.td() - remove_type = sequence is None or not period.origin and "submit" or "checkbox" + remove_type = not obj.is_shared() or not period.origin and "submit" or "checkbox" self._control("recur-remove", remove_type, str(index), str(index) in args.get("recur-remove", []), id="recur-remove-%d" % index, class_="remove")