imip-agent

Changeset

619:b021078b64d4
2015-07-29 Paul Boddie raw files shortlog changelog graph Tidied up somewhat, moving update_attendees into the client abstraction and adjusting methods in the manager's event module.
imiptools/client.py (file) imiptools/data.py (file) imipweb/event.py (file)
     1.1 --- a/imiptools/client.py	Wed Jul 29 16:40:28 2015 +0200
     1.2 +++ b/imiptools/client.py	Wed Jul 29 18:41:11 2015 +0200
     1.3 @@ -32,49 +32,6 @@
     1.4  from imiptools.profile import Preferences
     1.5  import imip_store
     1.6  
     1.7 -def update_attendees(obj, attendees, removed):
     1.8 -
     1.9 -    """
    1.10 -    Update the attendees in 'obj' with the given 'attendees' and 'removed'
    1.11 -    attendee lists. A list is returned containing the attendees whose
    1.12 -    attendance should be cancelled.
    1.13 -    """
    1.14 -
    1.15 -    to_cancel = []
    1.16 -
    1.17 -    existing_attendees = uri_values(obj.get_values("ATTENDEE") or [])
    1.18 -    added = set(attendees).difference(existing_attendees)
    1.19 -
    1.20 -    if added or removed:
    1.21 -        attendees = uri_items(obj.get_items("ATTENDEE") or [])
    1.22 -        sequence = obj.get_value("SEQUENCE")
    1.23 -
    1.24 -        if removed:
    1.25 -            remaining = []
    1.26 -
    1.27 -            for attendee, attendee_attr in attendees:
    1.28 -                if attendee in removed:
    1.29 -
    1.30 -                    # Without a sequence number, assume that the event has not
    1.31 -                    # been published and that attendees can be silently removed.
    1.32 -
    1.33 -                    if sequence is not None:
    1.34 -                        to_cancel.append((attendee, attendee_attr))
    1.35 -                else:
    1.36 -                    remaining.append((attendee, attendee_attr))
    1.37 -
    1.38 -            attendees = remaining
    1.39 -
    1.40 -        if added:
    1.41 -            for attendee in added:
    1.42 -                attendee = attendee.strip()
    1.43 -                if attendee:
    1.44 -                    attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"}))
    1.45 -
    1.46 -        obj["ATTENDEE"] = attendees
    1.47 -
    1.48 -    return to_cancel
    1.49 -
    1.50  class Client:
    1.51  
    1.52      "Common handler and manager methods."
    1.53 @@ -126,6 +83,49 @@
    1.54  
    1.55      # Common operations on calendar data.
    1.56  
    1.57 +    def update_attendees(self, obj, attendees, removed):
    1.58 +
    1.59 +        """
    1.60 +        Update the attendees in 'obj' with the given 'attendees' and 'removed'
    1.61 +        attendee lists. A list is returned containing the attendees whose
    1.62 +        attendance should be cancelled.
    1.63 +        """
    1.64 +
    1.65 +        to_cancel = []
    1.66 +
    1.67 +        existing_attendees = uri_values(obj.get_values("ATTENDEE") or [])
    1.68 +        added = set(attendees).difference(existing_attendees)
    1.69 +
    1.70 +        if added or removed:
    1.71 +            attendees = uri_items(obj.get_items("ATTENDEE") or [])
    1.72 +            sequence = obj.get_value("SEQUENCE")
    1.73 +
    1.74 +            if removed:
    1.75 +                remaining = []
    1.76 +
    1.77 +                for attendee, attendee_attr in attendees:
    1.78 +                    if attendee in removed:
    1.79 +
    1.80 +                        # Without a sequence number, assume that the event has not
    1.81 +                        # been published and that attendees can be silently removed.
    1.82 +
    1.83 +                        if sequence is not None:
    1.84 +                            to_cancel.append((attendee, attendee_attr))
    1.85 +                    else:
    1.86 +                        remaining.append((attendee, attendee_attr))
    1.87 +
    1.88 +                attendees = remaining
    1.89 +
    1.90 +            if added:
    1.91 +                for attendee in added:
    1.92 +                    attendee = attendee.strip()
    1.93 +                    if attendee:
    1.94 +                        attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"}))
    1.95 +
    1.96 +            obj["ATTENDEE"] = attendees
    1.97 +
    1.98 +        return to_cancel
    1.99 +
   1.100      def update_participation(self, obj, partstat=None):
   1.101  
   1.102          """
     2.1 --- a/imiptools/data.py	Wed Jul 29 16:40:28 2015 +0200
     2.2 +++ b/imiptools/data.py	Wed Jul 29 18:41:11 2015 +0200
     2.3 @@ -149,6 +149,15 @@
     2.4          dtend, dtend_attr = self.get_datetime_item("DTEND")
     2.5          return get_tzid(dtstart_attr, dtend_attr)
     2.6  
     2.7 +    def is_shared(self):
     2.8 +
     2.9 +        """
    2.10 +        Return whether this object is shared based on the presence of a SEQUENCE
    2.11 +        property.
    2.12 +        """
    2.13 +
    2.14 +        return self.get_value("SEQUENCE") is not None
    2.15 +
    2.16  # Construction and serialisation.
    2.17  
    2.18  def make_calendar(nodes, method=None):
     3.1 --- a/imipweb/event.py	Wed Jul 29 16:40:28 2015 +0200
     3.2 +++ b/imipweb/event.py	Wed Jul 29 18:41:11 2015 +0200
     3.3 @@ -20,7 +20,6 @@
     3.4  """
     3.5  
     3.6  from datetime import date, timedelta
     3.7 -from imiptools.client import update_attendees
     3.8  from imiptools.data import get_uri, uri_dict, uri_values
     3.9  from imiptools.dates import format_datetime, get_datetime_item, \
    3.10                              get_period_item, to_date, to_timezone
    3.11 @@ -60,11 +59,51 @@
    3.12          (None, "Not indicated"),
    3.13          ]
    3.14  
    3.15 +    # Access to stored object information.
    3.16 +
    3.17      def is_organiser(self, obj):
    3.18          return get_uri(obj.get_value("ORGANIZER")) == self.user
    3.19  
    3.20 +    def get_stored_attendees(self, obj):
    3.21 +        return uri_values(obj.get_values("ATTENDEE") or [])
    3.22 +
    3.23 +    def get_stored_main_period(self, obj):
    3.24 +
    3.25 +        """
    3.26 +        Return the main event period for the given 'obj'.
    3.27 +        """
    3.28 +
    3.29 +        dtstart, dtstart_attr = obj.get_datetime_item("DTSTART")
    3.30 +
    3.31 +        if obj.has_key("DTEND"):
    3.32 +            dtend, dtend_attr = obj.get_datetime_item("DTEND")
    3.33 +        elif obj.has_key("DURATION"):
    3.34 +            duration = obj.get_duration("DURATION")
    3.35 +            dtend = dtstart + duration
    3.36 +            dtend_attr = dtstart_attr
    3.37 +        else:
    3.38 +            dtend, dtend_attr = dtstart, dtstart_attr
    3.39 +
    3.40 +        return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr)
    3.41 +
    3.42 +    def get_stored_recurrences(self, obj):
    3.43 +
    3.44 +        "Return recurrences computed using the given 'obj'."
    3.45 +
    3.46 +        recurrences = []
    3.47 +        for period in self.get_periods(obj):
    3.48 +            if period.origin != "DTSTART":
    3.49 +                recurrences.append(period)
    3.50 +        return recurrences
    3.51 +
    3.52      # Request logic methods.
    3.53  
    3.54 +    def is_initial_load(self):
    3.55 +
    3.56 +        "Return whether the event is being loaded and shown for the first time."
    3.57 +
    3.58 +        return not self.env.get_args().has_key("editing")
    3.59 +
    3.60      def handle_request(self, obj):
    3.61  
    3.62          """
    3.63 @@ -124,9 +163,7 @@
    3.64                  # Set the periods in the object, first obtaining removed and
    3.65                  # modified period information.
    3.66  
    3.67 -                to_unschedule = []
    3.68 -                for i in args.get("recur-remove", []):
    3.69 -                    to_unschedule.append(periods[int(i)])
    3.70 +                to_unschedule = self.get_removed_periods()
    3.71  
    3.72                  self.set_period_in_object(obj, period)
    3.73                  self.set_periods_in_object(obj, periods)
    3.74 @@ -138,9 +175,9 @@
    3.75  
    3.76                  # Obtain any participants and those to be removed.
    3.77  
    3.78 -                attendees = self.get_attendees()
    3.79 +                attendees = self.get_attendees_from_page()
    3.80                  removed = [attendees[int(i)] for i in args.get("remove", [])]
    3.81 -                to_cancel = update_attendees(obj, attendees, removed)
    3.82 +                to_cancel = self.update_attendees(obj, attendees, removed)
    3.83                  single_user = not attendees or attendees == [self.user]
    3.84  
    3.85              # Update attendee participation for the current user.
    3.86 @@ -291,29 +328,16 @@
    3.87          return all_values
    3.88  
    3.89      def get_current_main_period(self, obj):
    3.90 -        if self.is_initial_load() or not self.is_organiser(obj):
    3.91 -            return self.get_existing_main_period(obj)
    3.92 -        else:
    3.93 -            return self.get_main_period()
    3.94 -
    3.95 -    def get_existing_main_period(self, obj):
    3.96  
    3.97          """
    3.98 -        Return the main event period for the given 'obj'.
    3.99 +        Return the currently active main period for 'obj' depending on whether
   3.100 +        editing has begun or whether the object has just been loaded.
   3.101          """
   3.102  
   3.103 -        dtstart, dtstart_attr = obj.get_datetime_item("DTSTART")
   3.104 -
   3.105 -        if obj.has_key("DTEND"):
   3.106 -            dtend, dtend_attr = obj.get_datetime_item("DTEND")
   3.107 -        elif obj.has_key("DURATION"):
   3.108 -            duration = obj.get_duration("DURATION")
   3.109 -            dtend = dtstart + duration
   3.110 -            dtend_attr = dtstart_attr
   3.111 +        if self.is_initial_load() or not self.is_organiser(obj):
   3.112 +            return self.get_stored_main_period(obj)
   3.113          else:
   3.114 -            dtend, dtend_attr = dtstart, dtstart_attr
   3.115 -
   3.116 -        return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr)
   3.117 +            return self.get_main_period()
   3.118  
   3.119      def get_main_period(self):
   3.120  
   3.121 @@ -336,20 +360,10 @@
   3.122          """
   3.123  
   3.124          if self.is_initial_load() or not self.is_organiser(obj):
   3.125 -            return self.get_existing_recurrences(obj)
   3.126 +            return self.get_stored_recurrences(obj)
   3.127          else:
   3.128              return self.get_recurrences()
   3.129  
   3.130 -    def get_existing_recurrences(self, obj):
   3.131 -
   3.132 -        "Return recurrences computed using the given 'obj'."
   3.133 -
   3.134 -        recurrences = []
   3.135 -        for period in self.get_periods(obj):
   3.136 -            if period.origin != "DTSTART":
   3.137 -                recurrences.append(period)
   3.138 -        return recurrences
   3.139 -
   3.140      def get_recurrences(self):
   3.141  
   3.142          "Return the recurrences defined in the event form."
   3.143 @@ -374,11 +388,15 @@
   3.144  
   3.145          return periods
   3.146  
   3.147 -    def is_initial_load(self):
   3.148 +    def get_removed_periods(self):
   3.149 +
   3.150 +        "Return a list of recurrence periods to remove upon updating an event."
   3.151  
   3.152 -        "Return whether the event is being loaded and shown for the first time."
   3.153 -
   3.154 -        return not self.env.get_args().has_key("editing")
   3.155 +        to_unschedule = []
   3.156 +        args = self.env.get_args()
   3.157 +        for i in args.get("recur-remove", []):
   3.158 +            to_unschedule.append(periods[int(i)])
   3.159 +        return to_unschedule
   3.160  
   3.161      def get_current_attendees(self, obj):
   3.162  
   3.163 @@ -388,14 +406,11 @@
   3.164          """
   3.165  
   3.166          if self.is_initial_load() or not self.is_organiser(obj):
   3.167 -            return self.get_existing_attendees(obj)
   3.168 +            return self.get_stored_attendees(obj)
   3.169          else:
   3.170 -            return self.get_attendees()
   3.171 +            return self.get_attendees_from_page()
   3.172  
   3.173 -    def get_existing_attendees(self, obj):
   3.174 -        return uri_values(obj.get_values("ATTENDEE") or [])
   3.175 -
   3.176 -    def get_attendees(self):
   3.177 +    def get_attendees_from_page(self):
   3.178  
   3.179          """
   3.180          Return attendees from the request, normalised for iCalendar purposes,
   3.181 @@ -418,15 +433,14 @@
   3.182  
   3.183          return ordered_attendees
   3.184  
   3.185 -    def update_attendees(self, obj):
   3.186 +    def update_attendees_from_page(self, obj):
   3.187  
   3.188          "Add or remove attendees. This does not affect the stored object."
   3.189  
   3.190          args = self.env.get_args()
   3.191  
   3.192 -        attendees = self.get_attendees()
   3.193 -        existing_attendees = self.get_existing_attendees(obj)
   3.194 -        sequence = obj.get_value("SEQUENCE")
   3.195 +        attendees = self.get_attendees_from_page()
   3.196 +        existing_attendees = self.get_stored_attendees(obj)
   3.197  
   3.198          if args.has_key("add"):
   3.199              attendees.append("")
   3.200 @@ -443,7 +457,7 @@
   3.201  
   3.202                  existing = attendee in existing_attendees
   3.203  
   3.204 -                if not existing or sequence is None or attendee == self.user:
   3.205 +                if not existing or not obj.is_shared() or attendee == self.user:
   3.206                      attendees.remove(attendee)
   3.207  
   3.208          return attendees
   3.209 @@ -460,7 +474,6 @@
   3.210          attendees = self.get_current_attendees(obj)
   3.211          is_attendee = self.user in attendees
   3.212          is_request = (obj.get_uid(), obj.get_recurrenceid()) in self._get_requests()
   3.213 -        sequence = obj.get_value("SEQUENCE")
   3.214  
   3.215          # Show appropriate options depending on the role of the user.
   3.216  
   3.217 @@ -479,10 +492,10 @@
   3.218              page.p("As organiser, you can perform the following:")
   3.219  
   3.220              page.p()
   3.221 -            self._control("create", "submit", sequence is None and "Create event" or "Update event")
   3.222 +            self._control("create", "submit", not obj.is_shared() and "Create event" or "Update event")
   3.223              page.add(" ")
   3.224  
   3.225 -            if sequence is not None and not is_request:
   3.226 +            if obj.is_shared() and not is_request:
   3.227                  self._control("cancel", "submit", "Cancel event")
   3.228              else:
   3.229                  self._control("discard", "submit", "Discard event")
   3.230 @@ -512,9 +525,9 @@
   3.231          # Obtain basic event information, generating any necessary editing controls.
   3.232  
   3.233          if self.is_initial_load() or not self.is_organiser(obj):
   3.234 -            attendees = self.get_existing_attendees(obj)
   3.235 +            attendees = self.get_stored_attendees(obj)
   3.236          else:
   3.237 -            attendees = self.update_attendees(obj)
   3.238 +            attendees = self.update_attendees_from_page(obj)
   3.239  
   3.240          p = self.get_current_main_period(obj)
   3.241          self.show_object_datetime_controls(p)
   3.242 @@ -650,13 +663,12 @@
   3.243  
   3.244          existing = attendee_attr is not None
   3.245          partstat = attendee_attr and attendee_attr.get("PARTSTAT")
   3.246 -        sequence = obj.get_value("SEQUENCE")
   3.247  
   3.248          page.td(class_="objectvalue")
   3.249  
   3.250          # Show a form control as organiser for new attendees.
   3.251  
   3.252 -        if self.is_organiser(obj) and (not existing or sequence is None):
   3.253 +        if self.is_organiser(obj) and (not existing or not obj.is_shared()):
   3.254              self._control("attendee", "value", attendee, size="40")
   3.255          else:
   3.256              self._control("attendee", "hidden", attendee)
   3.257 @@ -684,7 +696,7 @@
   3.258  
   3.259              # Permit the removal of newly-added attendees.
   3.260  
   3.261 -            remove_type = (not existing or sequence is None or attendee == self.user) and "submit" or "checkbox"
   3.262 +            remove_type = (not existing or not obj.is_shared() or attendee == self.user) and "submit" or "checkbox"
   3.263              self._control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove")
   3.264  
   3.265              page.label("Remove", for_="remove-%d" % i, class_="remove")
   3.266 @@ -773,8 +785,6 @@
   3.267          page = self.page
   3.268          args = self.env.get_args()
   3.269  
   3.270 -        sequence = obj.get_value("SEQUENCE")
   3.271 -
   3.272          p = event_period_from_period(period)
   3.273          replaced = not recurrenceid and self.is_replaced(p, recurrenceids)
   3.274  
   3.275 @@ -806,7 +816,7 @@
   3.276              page.th("")
   3.277              page.td()
   3.278  
   3.279 -            remove_type = sequence is None or not period.origin and "submit" or "checkbox"
   3.280 +            remove_type = not obj.is_shared() or not period.origin and "submit" or "checkbox"
   3.281              self._control("recur-remove", remove_type, str(index),
   3.282                  str(index) in args.get("recur-remove", []),
   3.283                  id="recur-remove-%d" % index, class_="remove")