imip-agent

Changeset

473:4837e47eb5d6
2015-04-01 Paul Boddie raw files shortlog changelog graph Simplified attendee definition, employing a single attendees collection when handling an event form, updating attendees upon saving or sending events, and separating this operation from the sending operation. Changed the mechanism of adding attendees to only show an editable field after adding an attendee, and making the participation status area act as a form refresh button to be able to provide a menu for the current user as attendee.
htdocs/styles.css (file) imiptools/client.py (file) imipweb/event.py (file) imipweb/handler.py (file)
     1.1 --- a/htdocs/styles.css	Wed Apr 01 00:00:16 2015 +0200
     1.2 +++ b/htdocs/styles.css	Wed Apr 01 01:31:39 2015 +0200
     1.3 @@ -184,6 +184,10 @@
     1.4  input.remove:checked ~ label.remove,
     1.5  input.remove:not(:checked) ~ label.removed,
     1.6  
     1.7 +/* Hide the participation refresh control, selected using a label. */
     1.8 +
     1.9 +input.refresh,
    1.10 +
    1.11  /* Hide the reset control, selected using a label. */
    1.12  
    1.13  input#reset {
     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