1.1 --- a/imiptools/editing.py Fri Oct 20 00:51:26 2017 +0200
1.2 +++ b/imiptools/editing.py Fri Oct 20 01:06:09 2017 +0200
1.3 @@ -23,7 +23,7 @@
1.4 from copy import copy
1.5 from datetime import datetime, timedelta
1.6 from imiptools.client import ClientForObject
1.7 -from imiptools.data import get_main_period
1.8 +from imiptools.data import get_main_period, uri_items
1.9 from imiptools.dates import end_date_from_calendar, end_date_to_calendar, \
1.10 format_datetime, get_datetime, \
1.11 get_datetime_attributes, get_end_of_day, \
1.12 @@ -125,12 +125,12 @@
1.13 "Reset the editing state."
1.14
1.15 self.state = State({
1.16 - "attendees" : lambda: OrderedDict(self.obj.get_items("ATTENDEE") or []),
1.17 - "organiser" : lambda: self.obj.get_value("ORGANIZER"),
1.18 - "periods" : lambda: form_periods_from_periods(self.get_unedited_periods()),
1.19 + "attendees" : lambda: OrderedDict(self.obj.get_items("ATTENDEE") or []),
1.20 + "organiser" : lambda: self.obj.get_value("ORGANIZER"),
1.21 + "periods" : lambda: form_periods_from_periods(self.get_unedited_periods()),
1.22 "suggested_attendees" : self.get_suggested_attendees,
1.23 - "suggested_periods" : self.get_suggested_periods,
1.24 - "summary" : lambda: self.obj.get_value("SUMMARY"),
1.25 + "suggested_periods" : self.get_suggested_periods,
1.26 + "summary" : lambda: self.obj.get_value("SUMMARY"),
1.27 })
1.28
1.29 # Access to stored and current information.
1.30 @@ -185,11 +185,16 @@
1.31
1.32 existing = self.state.get("attendees")
1.33 l = []
1.34 +
1.35 for attendee, objects in self.get_counters().items():
1.36 for obj in objects:
1.37 for suggested, attr in obj.get_items("ATTENDEE"):
1.38 if suggested not in existing:
1.39 l.append((attendee, (suggested, attr)))
1.40 +
1.41 + # Provide a stable ordering.
1.42 +
1.43 + l.sort()
1.44 return l
1.45
1.46 def get_suggested_periods(self):
1.47 @@ -239,8 +244,60 @@
1.48 for period in removed:
1.49 suggested.append((attendee, period, "remove"))
1.50
1.51 + # Provide a stable ordering.
1.52 +
1.53 + suggested.sort()
1.54 return suggested
1.55
1.56 + def get_conflicting_periods(self):
1.57 + periods = self.state.get("periods")
1.58 + attendees = self.state.get("attendees")
1.59 + conflicts = set()
1.60 +
1.61 + for attendee, attr in uri_items(attendees.items()):
1.62 + if not attendee:
1.63 + continue
1.64 +
1.65 + # If not attending this event, other periods cannot conflict.
1.66 +
1.67 + if not attr.get("PARTSTAT") in ("ACCEPTED", "TENTATIVE"):
1.68 + continue
1.69 +
1.70 + # Obtain free/busy details for the attendee.
1.71 +
1.72 + if attendee == self.user:
1.73 + freebusy = self.store.get_freebusy(attendee)
1.74 + elif attendee:
1.75 + freebusy = self.store.get_freebusy_for_other(self.user, attendee)
1.76 + else:
1.77 + continue
1.78 +
1.79 + # Without free/busy information, no conflicts can be determined for
1.80 + # the user.
1.81 +
1.82 + if not freebusy:
1.83 + continue
1.84 +
1.85 + # Compare the origin of each conflicting period.
1.86 +
1.87 + for p in freebusy.have_conflict(periods, True):
1.88 +
1.89 + # Ignore transparent periods.
1.90 +
1.91 + if p.transp == "ORG":
1.92 + continue
1.93 +
1.94 + # Prevent conflicts with this event's own periods.
1.95 +
1.96 + if p.uid == self.uid and (not self.recurrenceid or
1.97 + not p.recurrenceid or
1.98 + self.recurrenceid == p.recurrenceid):
1.99 + continue
1.100 +
1.101 + conflicts.add(p)
1.102 +
1.103 + return conflicts
1.104 +
1.105 # Validation methods.
1.106
1.107 def get_checked_periods(self):
1.108 @@ -620,7 +677,7 @@
1.109
1.110 try:
1.111 attr = attendees[attendee]
1.112 - if self.is_organiser() and not self.obj.is_shared() or not attr:
1.113 + if self.is_organiser() or not attr:
1.114 return (attendee, attr)
1.115 except IndexError:
1.116 pass