2.1 --- a/imiptools/client.py Wed Apr 01 00:00:16 2015 +0200
2.2 +++ b/imiptools/client.py Wed Apr 01 01:31:39 2015 +0200
2.3 @@ -19,20 +19,23 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 -from imiptools.data import get_window_end, uri_items
2.8 +from imiptools.data import get_uri, get_window_end, uri_dict, uri_items, uri_values
2.9 from imiptools.dates import get_default_timezone
2.10 from imiptools.profile import Preferences
2.11
2.12 -def update_attendees(obj, added, removed):
2.13 +def update_attendees(obj, attendees, removed):
2.14
2.15 """
2.16 - Update the attendees in 'obj' with the given 'added' and 'removed'
2.17 + Update the attendees in 'obj' with the given 'attendees' and 'removed'
2.18 attendee lists. A list is returned containing the attendees whose
2.19 attendance should be cancelled.
2.20 """
2.21
2.22 to_cancel = []
2.23
2.24 + existing_attendees = uri_values(obj.get_values("ATTENDEE") or [])
2.25 + added = set(attendees).difference(existing_attendees)
2.26 +
2.27 if added or removed:
2.28 attendees = uri_items(obj.get_items("ATTENDEE") or [])
2.29 sequence = obj.get_value("SEQUENCE")
2.30 @@ -55,12 +58,26 @@
2.31
2.32 if added:
2.33 for attendee in added:
2.34 - attendees.append((attendee, {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"}))
2.35 + attendee = attendee.strip()
2.36 + if attendee:
2.37 + attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"}))
2.38
2.39 obj["ATTENDEE"] = attendees
2.40
2.41 return to_cancel
2.42
2.43 +def update_participation(obj, user, partstat):
2.44 +
2.45 + "Update the participation in 'obj' of 'user' with the given 'partstat'."
2.46 +
2.47 + existing_attendees = uri_dict(obj.get_value_map("ATTENDEE"))
2.48 +
2.49 + if partstat:
2.50 + if existing_attendees.has_key(user):
2.51 + existing_attendees[user]["PARTSTAT"] = partstat
2.52 + if existing_attendees[user].has_key("RSVP"):
2.53 + del existing_attendees[user]["RSVP"]
2.54 +
2.55 class Client:
2.56
2.57 "Common handler and manager methods."
3.1 --- a/imipweb/event.py Wed Apr 01 00:00:16 2015 +0200
3.2 +++ b/imipweb/event.py Wed Apr 01 01:31:39 2015 +0200
3.3 @@ -20,7 +20,7 @@
3.4 """
3.5
3.6 from datetime import datetime, timedelta
3.7 -from imiptools.client import update_attendees
3.8 +from imiptools.client import update_attendees, update_participation
3.9 from imiptools.data import get_uri, uri_dict, uri_values
3.10 from imiptools.dates import format_datetime, to_date, get_datetime, \
3.11 get_datetime_item, get_period_item, \
3.12 @@ -89,31 +89,31 @@
3.13
3.14 if reply or invite or cancel or save:
3.15
3.16 - # Update time periods (main and recurring).
3.17 + # Update principal event details if organiser.
3.18 +
3.19 + if is_organiser:
3.20 +
3.21 + # Update time periods (main and recurring).
3.22 +
3.23 + if periods:
3.24 + self.set_period_in_object(obj, periods[0])
3.25 + self.set_periods_in_object(obj, periods[1:])
3.26
3.27 - if periods:
3.28 - self.set_period_in_object(obj, periods[0])
3.29 - self.set_periods_in_object(obj, periods[1:])
3.30 + # Update summary.
3.31 +
3.32 + if args.has_key("summary"):
3.33 + obj["SUMMARY"] = [(args["summary"][0], {})]
3.34
3.35 - # Update summary.
3.36 + # Obtain any participants and those to be removed.
3.37
3.38 - if args.has_key("summary"):
3.39 - obj["SUMMARY"] = [(args["summary"][0], {})]
3.40 + attendees = args.get("attendee")
3.41 + removed = args.get("remove")
3.42 + to_cancel = update_attendees(obj, attendees, removed)
3.43
3.44 # Update attendee participation.
3.45
3.46 - attendees = uri_dict(obj.get_value_map("ATTENDEE"))
3.47 -
3.48 if args.has_key("partstat"):
3.49 - if attendees.has_key(self.user):
3.50 - attendees[self.user]["PARTSTAT"] = args["partstat"][0]
3.51 - if attendees[self.user].has_key("RSVP"):
3.52 - del attendees[self.user]["RSVP"]
3.53 -
3.54 - # Obtain any participants to be added or removed.
3.55 -
3.56 - removed = args.get("remove")
3.57 - added = args.get("added")
3.58 + update_participation(obj, self.user, args["partstat"][0])
3.59
3.60 # Process any action.
3.61
3.62 @@ -125,16 +125,19 @@
3.63
3.64 # Process the object and remove it from the list of requests.
3.65
3.66 - if reply and handler.process_received_request(update) or \
3.67 - is_organiser and (invite or cancel) and \
3.68 - handler.process_created_request(invite and "REQUEST" or "CANCEL", update, removed, added):
3.69 + if reply and handler.process_received_request(update):
3.70 + self.remove_request(uid, recurrenceid)
3.71 +
3.72 + elif is_organiser and (invite or cancel):
3.73
3.74 - self.remove_request(uid, recurrenceid)
3.75 + if handler.process_created_request(
3.76 + invite and "REQUEST" or "CANCEL", update, to_cancel):
3.77 +
3.78 + self.remove_request(uid, recurrenceid)
3.79
3.80 # Save single user events.
3.81
3.82 elif save:
3.83 - to_cancel = update_attendees(obj, added, removed)
3.84 self.store.set_event(self.user, uid, recurrenceid, node=obj.to_node())
3.85 self.update_freebusy(uid, recurrenceid, obj)
3.86 self.remove_request(uid, recurrenceid)
3.87 @@ -367,29 +370,23 @@
3.88
3.89 return False
3.90
3.91 - def handle_new_attendees(self, obj):
3.92 + def handle_attendees(self, obj):
3.93
3.94 - "Add or remove new attendees. This does not affect the stored object."
3.95 + "Add or remove attendees. This does not affect the stored object."
3.96
3.97 args = self.env.get_args()
3.98
3.99 - existing_attendees = uri_values(obj.get_values("ATTENDEE") or [])
3.100 - new_attendees = args.get("added", [])
3.101 - new_attendee = args.get("attendee", [""])[0]
3.102 + attendees = args.get("attendee", [])
3.103
3.104 if args.has_key("add"):
3.105 - if new_attendee.strip():
3.106 - new_attendee = get_uri(new_attendee.strip())
3.107 - if new_attendee not in new_attendees and new_attendee not in existing_attendees:
3.108 - new_attendees.append(new_attendee)
3.109 - new_attendee = ""
3.110 + attendees.append("")
3.111
3.112 - if args.has_key("removenew"):
3.113 - removed_attendee = args["removenew"][0]
3.114 - if removed_attendee in new_attendees:
3.115 - new_attendees.remove(removed_attendee)
3.116 + if args.has_key("remove"):
3.117 + removed_attendee = args["remove"][0]
3.118 + if removed_attendee in attendees:
3.119 + attendees.remove(removed_attendee)
3.120
3.121 - return new_attendees, new_attendee
3.122 + return attendees
3.123
3.124 def get_event_period(self, obj):
3.125
3.126 @@ -501,12 +498,10 @@
3.127 # Obtain basic event information, showing any necessary editing controls.
3.128
3.129 is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user
3.130 + initial_load = not args.has_key("editing")
3.131
3.132 - if is_organiser:
3.133 - new_attendees, new_attendee = self.handle_new_attendees(obj)
3.134 - else:
3.135 - new_attendees = []
3.136 - new_attendee = ""
3.137 + attendees = is_organiser and self.handle_attendees(obj) or \
3.138 + (initial_load or not is_organiser) and uri_values(obj.get_values("ATTENDEE")) or []
3.139
3.140 (dtstart, dtstart_attr), (dtend, dtend_attr) = self.get_event_period(obj)
3.141 self.show_object_datetime_controls(dtstart, dtend)
3.142 @@ -528,7 +523,7 @@
3.143 rowspan = len(items)
3.144
3.145 if name == "ATTENDEE":
3.146 - rowspan += len(new_attendees) + 1
3.147 + rowspan = len(attendees) + 1 # for the add button
3.148 elif not items:
3.149 continue
3.150
3.151 @@ -568,7 +563,83 @@
3.152 page.td.close()
3.153 page.tr.close()
3.154
3.155 - # Handle potentially many values.
3.156 + # Handle attendees specially.
3.157 +
3.158 + elif name == "ATTENDEE":
3.159 + attendee_map = dict(items)
3.160 + first = True
3.161 +
3.162 + for i, value in enumerate(attendees):
3.163 + if not first:
3.164 + page.tr()
3.165 + else:
3.166 + first = False
3.167 +
3.168 + page.td(class_="objectvalue")
3.169 +
3.170 + # Obtain details of existing attendees.
3.171 +
3.172 + attr = attendee_map.get(value)
3.173 + partstat = attr and attr.get("PARTSTAT")
3.174 +
3.175 + # Show a form control as organiser for new attendees.
3.176 +
3.177 + if is_organiser and not partstat:
3.178 + page.input(name="attendee", type="value", value=value, size="40")
3.179 + else:
3.180 + page.input(name="attendee", type="hidden", value=value)
3.181 + page.add(value)
3.182 + page.add(" ")
3.183 +
3.184 + # Show participation status, editable for the current user.
3.185 +
3.186 + if value == self.user:
3.187 + self._show_menu("partstat", partstat, self.partstat_items, "partstat")
3.188 +
3.189 + # Allow the participation indicator to act as a submit
3.190 + # button in order to refresh the page and show a control for
3.191 + # the current user, if indicated.
3.192 +
3.193 + elif is_organiser:
3.194 + page.input(name="partstat-refresh", type="submit", value="refresh", id="partstat-%d" % i, class_="refresh")
3.195 + page.label(dict(self.partstat_items).get(partstat, ""), for_="partstat-%s" % i, class_="partstat")
3.196 + else:
3.197 + page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat")
3.198 +
3.199 + # Permit organisers to remove attendees.
3.200 +
3.201 + if is_organiser:
3.202 +
3.203 + # Permit the removal of newly-added attendees.
3.204 +
3.205 + remove_type = partstat and "checkbox" or "submit"
3.206 +
3.207 + if value in args.get("remove", []):
3.208 + page.input(name="remove", type=remove_type, value=value, id="remove-%d" % i, class_="remove", checked="checked")
3.209 + else:
3.210 + page.input(name="remove", type=remove_type, value=value, id="remove-%d" % i, class_="remove")
3.211 +
3.212 + page.label("Remove", for_="remove-%d" % i, class_="remove")
3.213 + page.label("Uninvited", for_="remove-%d" % i, class_="removed")
3.214 +
3.215 + page.td.close()
3.216 + page.tr.close()
3.217 +
3.218 + # Allow more attendees to be specified.
3.219 +
3.220 + if is_organiser:
3.221 + i = len(attendees)
3.222 +
3.223 + if not first:
3.224 + page.tr()
3.225 +
3.226 + page.td()
3.227 + page.input(name="add", type="submit", value="add", id="add-%d" % i, class_="add")
3.228 + page.label("Add attendee", for_="add-%d" % i, class_="add")
3.229 + page.td.close()
3.230 + page.tr.close()
3.231 +
3.232 + # Handle potentially many values of other kinds.
3.233
3.234 else:
3.235 first = True
3.236 @@ -579,57 +650,8 @@
3.237 else:
3.238 first = False
3.239
3.240 - if name == "ATTENDEE":
3.241 - value = get_uri(value)
3.242 -
3.243 - page.td(class_="objectvalue")
3.244 - page.add(value)
3.245 - page.add(" ")
3.246 -
3.247 - partstat = attr.get("PARTSTAT")
3.248 - if value == self.user:
3.249 - self._show_menu("partstat", partstat, self.partstat_items, "partstat")
3.250 - else:
3.251 - page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat")
3.252 -
3.253 - if is_organiser:
3.254 - if value in args.get("remove", []):
3.255 - page.input(name="remove", type="checkbox", value=value, id="remove-%d" % i, class_="remove", checked="checked")
3.256 - else:
3.257 - page.input(name="remove", type="checkbox", value=value, id="remove-%d" % i, class_="remove")
3.258 - page.label("Remove", for_="remove-%d" % i, class_="remove")
3.259 - page.label("Uninvited", for_="remove-%d" % i, class_="removed")
3.260 -
3.261 - else:
3.262 - page.td(class_="objectvalue")
3.263 - page.add(value)
3.264 -
3.265 - page.td.close()
3.266 - page.tr.close()
3.267 -
3.268 - # Allow more attendees to be specified.
3.269 -
3.270 - if is_organiser and name == "ATTENDEE":
3.271 - for i, attendee in enumerate(new_attendees):
3.272 - if not first:
3.273 - page.tr()
3.274 - else:
3.275 - first = False
3.276 -
3.277 - page.td()
3.278 - page.input(name="added", type="value", value=attendee, size="40")
3.279 - page.input(name="removenew", type="submit", value=attendee, id="removenew-%d" % i, class_="remove")
3.280 - page.label("Remove", for_="removenew-%d" % i, class_="remove")
3.281 - page.td.close()
3.282 - page.tr.close()
3.283 -
3.284 - if not first:
3.285 - page.tr()
3.286 -
3.287 - page.td()
3.288 - page.input(name="attendee", type="value", value=new_attendee, size="40")
3.289 - page.input(name="add", type="submit", value="add", id="add-%d" % i, class_="add")
3.290 - page.label("Add", for_="add-%d" % i, class_="add")
3.291 + page.td(class_="objectvalue")
3.292 + page.add(value)
3.293 page.td.close()
3.294 page.tr.close()
3.295
4.1 --- a/imipweb/handler.py Wed Apr 01 00:00:16 2015 +0200
4.2 +++ b/imipweb/handler.py Wed Apr 01 01:31:39 2015 +0200
4.3 @@ -19,8 +19,8 @@
4.4 this program. If not, see <http://www.gnu.org/licenses/>.
4.5 """
4.6
4.7 -from imiptools.client import Client, update_attendees
4.8 -from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \
4.9 +from imiptools.client import Client
4.10 +from imiptools.data import get_address, get_uri, make_freebusy, \
4.11 to_part, uri_item, uri_items, uri_values
4.12 from imiptools.dates import get_timestamp
4.13 from imiptools.handlers import Handler
4.14 @@ -131,20 +131,17 @@
4.15
4.16 return False
4.17
4.18 - def process_created_request(self, method, update=False, removed=None, added=None):
4.19 + def process_created_request(self, method, update=False, to_cancel=None):
4.20
4.21 """
4.22 - Process the current request for the given 'user', sending a created
4.23 - request of the given 'method' to attendees. Return whether any action
4.24 - was taken.
4.25 + Process the current request, sending a created request of the given
4.26 + 'method' to attendees. Return whether any action was taken.
4.27
4.28 If 'update' is given, the sequence number will be incremented in order
4.29 to override any previous message.
4.30
4.31 - If 'removed' is specified, a list of participants to be removed is
4.32 - provided.
4.33 -
4.34 - If 'added' is specified, a list of participants to be added is provided.
4.35 + If 'to_cancel' is specified, a list of participants to be sent cancel
4.36 + messages is provided.
4.37 """
4.38
4.39 organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
4.40 @@ -152,10 +149,6 @@
4.41 if self.messenger and self.messenger.sender != get_address(organiser):
4.42 organiser_attr["SENT-BY"] = get_uri(self.messenger.sender)
4.43
4.44 - # Update the attendees in the event.
4.45 -
4.46 - to_cancel = update_attendees(self.obj, added, removed)
4.47 -
4.48 self.update_dtstamp()
4.49 self.set_sequence(update)
4.50