1.1 --- a/imiptools/client.py Mon Oct 12 17:41:06 2015 +0200
1.2 +++ b/imiptools/client.py Mon Oct 12 17:42:03 2015 +0200
1.3 @@ -23,7 +23,7 @@
1.4 from imiptools import config
1.5 from imiptools.data import Object, get_address, get_uri, get_window_end, \
1.6 is_new_object, make_freebusy, to_part, \
1.7 - uri_dict, uri_items, uri_values
1.8 + uri_dict, uri_items, uri_parts, uri_values
1.9 from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \
1.10 get_duration, get_timestamp
1.11 from imiptools.period import can_schedule, remove_period, \
1.12 @@ -194,65 +194,6 @@
1.13
1.14 # Common operations on calendar data.
1.15
1.16 - def update_attendees(self, obj, attendees, removed):
1.17 -
1.18 - """
1.19 - Update the attendees in 'obj' with the given 'attendees' and 'removed'
1.20 - attendee lists. A list is returned containing the attendees whose
1.21 - attendance should be cancelled.
1.22 - """
1.23 -
1.24 - to_cancel = []
1.25 -
1.26 - existing_attendees = uri_values(obj.get_values("ATTENDEE") or [])
1.27 - added = set(attendees).difference(existing_attendees)
1.28 -
1.29 - if added or removed:
1.30 - attendees = uri_items(obj.get_items("ATTENDEE") or [])
1.31 - sequence = obj.get_value("SEQUENCE")
1.32 -
1.33 - if removed:
1.34 - remaining = []
1.35 -
1.36 - for attendee, attendee_attr in attendees:
1.37 - if attendee in removed:
1.38 -
1.39 - # Without a sequence number, assume that the event has not
1.40 - # been published and that attendees can be silently removed.
1.41 -
1.42 - if sequence is not None:
1.43 - to_cancel.append((attendee, attendee_attr))
1.44 - else:
1.45 - remaining.append((attendee, attendee_attr))
1.46 -
1.47 - attendees = remaining
1.48 -
1.49 - if added:
1.50 - for attendee in added:
1.51 - attendee = attendee.strip()
1.52 - if attendee:
1.53 - attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"}))
1.54 -
1.55 - obj["ATTENDEE"] = attendees
1.56 -
1.57 - return to_cancel
1.58 -
1.59 - def update_participation(self, obj, partstat=None):
1.60 -
1.61 - """
1.62 - Update the participation in 'obj' of the user with the given 'partstat'.
1.63 - """
1.64 -
1.65 - attendee_attr = uri_dict(obj.get_value_map("ATTENDEE")).get(self.user)
1.66 - if not attendee_attr:
1.67 - return None
1.68 - if partstat:
1.69 - attendee_attr["PARTSTAT"] = partstat
1.70 - if attendee_attr.has_key("RSVP"):
1.71 - del attendee_attr["RSVP"]
1.72 - self.update_sender(attendee_attr)
1.73 - return attendee_attr
1.74 -
1.75 def update_sender(self, attr):
1.76
1.77 "Update the SENT-BY attribute of the 'attr' sender metadata."
1.78 @@ -358,6 +299,14 @@
1.79
1.80 return True
1.81
1.82 + def is_organiser(self):
1.83 +
1.84 + """
1.85 + Return whether the current user is the organiser in the current object.
1.86 + """
1.87 +
1.88 + return get_uri(self.obj.get_value("ORGANIZER")) == self.user
1.89 +
1.90 # Object update methods.
1.91
1.92 def update_recurrenceid(self):
1.93 @@ -417,6 +366,105 @@
1.94
1.95 return True
1.96
1.97 + def update_attendees(self, attendees, removed):
1.98 +
1.99 + """
1.100 + Update the attendees in the current object with the given 'attendees'
1.101 + and 'removed' attendee lists.
1.102 +
1.103 + A tuple is returned containing two items: a list of the attendees whose
1.104 + attendance is being proposed (in a counter-proposal), a list of the
1.105 + attendees whose attendance should be cancelled.
1.106 + """
1.107 +
1.108 + to_cancel = []
1.109 +
1.110 + existing_attendees = uri_items(self.obj.get_items("ATTENDEE") or [])
1.111 + existing_attendees_map = dict(existing_attendees)
1.112 +
1.113 + # Added attendees are those from the supplied collection not already
1.114 + # present in the object.
1.115 +
1.116 + added = set(uri_values(attendees)).difference([uri for uri, attr in existing_attendees])
1.117 +
1.118 + # NOTE: When countering, no removals will occur, but additions might.
1.119 +
1.120 + if added or removed:
1.121 +
1.122 + # The organiser can remove existing attendees.
1.123 +
1.124 + if removed and self.is_organiser():
1.125 + remaining = []
1.126 +
1.127 + for attendee, attendee_attr in existing_attendees:
1.128 + if attendee in removed:
1.129 +
1.130 + # Only when an event has not been published can
1.131 + # attendees be silently removed.
1.132 +
1.133 + if obj.is_shared():
1.134 + to_cancel.append((attendee, attendee_attr))
1.135 + else:
1.136 + remaining.append((attendee, attendee_attr))
1.137 +
1.138 + existing_attendees = remaining
1.139 +
1.140 + # Attendees (when countering) must only include the current user and
1.141 + # any added attendees.
1.142 +
1.143 + elif not self.is_organiser():
1.144 + existing_attendees = []
1.145 +
1.146 + # Both organisers and attendees (when countering) can add attendees.
1.147 +
1.148 + if added:
1.149 +
1.150 + # Obtain a mapping from URIs to name details.
1.151 +
1.152 + attendee_map = dict([(attendee_uri, cn) for cn, attendee_uri in uri_parts(attendees)])
1.153 +
1.154 + for attendee in added:
1.155 + attendee = attendee.strip()
1.156 + if attendee:
1.157 + cn = attendee_map.get(attendee)
1.158 + attendee_attr = {"CN" : cn} or {}
1.159 +
1.160 + # Only the organiser can reset the participation attributes.
1.161 +
1.162 + if self.is_organiser():
1.163 + attendee_attr.update({"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"})
1.164 +
1.165 + existing_attendees.append((attendee, attendee_attr))
1.166 +
1.167 + # Attendees (when countering) must only include the current user and
1.168 + # any added attendees.
1.169 +
1.170 + if not self.is_organiser() and self.user not in existing_attendees:
1.171 + user_attr = self.get_user_attributes()
1.172 + user_attr.update(existing_attendees_map.get(self.user) or {})
1.173 + existing_attendees.append((self.user, user_attr))
1.174 +
1.175 + self.obj["ATTENDEE"] = existing_attendees
1.176 +
1.177 + return added, to_cancel
1.178 +
1.179 + def update_participation(self, partstat=None):
1.180 +
1.181 + """
1.182 + Update the participation in the current object of the user with the
1.183 + given 'partstat'.
1.184 + """
1.185 +
1.186 + attendee_attr = uri_dict(self.obj.get_value_map("ATTENDEE")).get(self.user)
1.187 + if not attendee_attr:
1.188 + return None
1.189 + if partstat:
1.190 + attendee_attr["PARTSTAT"] = partstat
1.191 + if attendee_attr.has_key("RSVP"):
1.192 + del attendee_attr["RSVP"]
1.193 + self.update_sender(attendee_attr)
1.194 + return attendee_attr
1.195 +
1.196 # Object-related tests.
1.197
1.198 def is_recognised_organiser(self, organiser):