imip-agent

imipweb/event.py

765:f2fa0b8bbcaf
2015-09-24 Paul Boddie Made a separate event page fragment class, separating request handling for the event page from the general presentation of events. imipweb-client-simplification
     1 #!/usr/bin/env python     2      3 """     4 A Web interface to a calendar event.     5      6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from imiptools.data import get_uri, uri_dict, uri_items, uri_values    23 from imiptools.dates import to_timezone    24 from imiptools.mail import Messenger    25 from imiptools.period import have_conflict    26 from imipweb.data import EventPeriod, event_period_from_period, FormPeriod, PeriodError    27 from imipweb.client import ManagerClient    28 from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject    29     30 class EventPageFragment(ResourceClientForObject, DateTimeFormUtilities, FormUtilities):    31     32     "A resource presenting the details of an event."    33     34     def __init__(self, resource=None):    35         ResourceClientForObject.__init__(self, resource)    36     37     # Various property values and labels.    38     39     property_items = [    40         ("SUMMARY", "Summary"),    41         ("DTSTART", "Start"),    42         ("DTEND", "End"),    43         ("ORGANIZER", "Organiser"),    44         ("ATTENDEE", "Attendee"),    45         ]    46     47     partstat_items = [    48         ("NEEDS-ACTION", "Not confirmed"),    49         ("ACCEPTED", "Attending"),    50         ("TENTATIVE", "Tentatively attending"),    51         ("DECLINED", "Not attending"),    52         ("DELEGATED", "Delegated"),    53         (None, "Not indicated"),    54         ]    55     56     def is_organiser(self):    57         return get_uri(self.obj.get_value("ORGANIZER")) == self.user    58     59     def can_remove_attendee(self, attendee):    60     61         """    62         Return whether 'attendee' can be removed from the current object without    63         notification.    64         """    65     66         return self.can_edit_attendee(attendee) or attendee == self.user    67     68     def can_edit_attendee(self, attendee):    69     70         "Return whether 'attendee' can be edited by an organiser."    71     72         return self.attendee_is_new(attendee) or not self.obj.is_shared()    73     74     def attendee_is_new(self, attendee):    75     76         "Return whether 'attendee' is new to the current object."    77     78         return attendee not in self.get_stored_attendees()    79     80     # Access to stored object information.    81     82     def get_stored_attendees(self):    83         return uri_values(self.obj.get_values("ATTENDEE") or [])    84     85     def get_stored_main_period(self):    86     87         "Return the main event period for the current object."    88     89         (dtstart, dtstart_attr), (dtend, dtend_attr) = self.obj.get_main_period_items(self.get_tzid())    90         return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr)    91     92     def get_stored_recurrences(self):    93     94         "Return recurrences computed using the current object."    95     96         recurrences = []    97         for period in self.get_periods(self.obj):    98             if period.origin != "DTSTART":    99                 recurrences.append(period)   100         return recurrences   101    102     # Access to current object information.   103    104     def get_current_main_period(self):   105         return self.get_stored_main_period()   106    107     def get_current_recurrences(self):   108         return self.get_stored_recurrences()   109    110     def get_current_attendees(self):   111         return self.get_stored_attendees()   112    113     # Page fragment methods.   114    115     def show_request_controls(self):   116    117         "Show form controls for a request."   118    119         page = self.page   120         args = self.env.get_args()   121    122         attendees = self.get_current_attendees()   123         is_attendee = self.user in attendees   124         is_request = self._have_request(self.uid, self.recurrenceid)   125    126         # Show appropriate options depending on the role of the user.   127    128         if is_attendee and not self.is_organiser():   129             page.p("An action is required for this request:")   130    131             page.p()   132             self.control("reply", "submit", "Send reply")   133             page.add(" ")   134             self.control("discard", "submit", "Discard event")   135             page.add(" ")   136             self.control("ignore", "submit", "Do nothing for now")   137             page.p.close()   138    139         if self.is_organiser():   140             page.p("As organiser, you can perform the following:")   141    142             page.p()   143             self.control("create", "submit", not self.obj.is_shared() and "Create event" or "Update event")   144             page.add(" ")   145    146             if self.obj.is_shared() and not is_request:   147                 self.control("cancel", "submit", "Cancel event")   148             else:   149                 self.control("discard", "submit", "Discard event")   150    151             page.add(" ")   152             self.control("save", "submit", "Save without sending")   153             page.p.close()   154    155     def show_object_on_page(self, errors=None):   156    157         """   158         Show the calendar object on the current page. If 'errors' is given, show   159         a suitable message for the different errors provided.   160         """   161    162         page = self.page   163         page.form(method="POST")   164    165         # Add a hidden control to help determine whether editing has already begun.   166    167         self.control("editing", "hidden", "true")   168    169         args = self.env.get_args()   170    171         # Obtain basic event information, generating any necessary editing controls.   172    173         attendees = self.update_current_attendees()   174         period = self.get_current_main_period()   175         self.show_object_datetime_controls(period)   176    177         # Obtain any separate recurrences for this event.   178    179         recurrenceids = self._get_active_recurrences(self.uid)   180         replaced = not self.recurrenceid and period.is_replaced(recurrenceids)   181    182         # Provide a summary of the object.   183    184         page.table(class_="object", cellspacing=5, cellpadding=5)   185         page.thead()   186         page.tr()   187         page.th("Event", class_="mainheading", colspan=2)   188         page.tr.close()   189         page.thead.close()   190         page.tbody()   191    192         for name, label in self.property_items:   193             field = name.lower()   194    195             items = uri_items(self.obj.get_items(name) or [])   196             rowspan = len(items)   197    198             if name == "ATTENDEE":   199                 rowspan = len(attendees) + 1 # for the add button   200             elif not items:   201                 continue   202    203             page.tr()   204             page.th(label, class_="objectheading %s%s" % (field, errors and field in errors and " error" or ""), rowspan=rowspan)   205    206             # Handle datetimes specially.   207    208             if name in ["DTSTART", "DTEND"]:   209                 if not replaced:   210    211                     # Obtain the datetime.   212    213                     is_start = name == "DTSTART"   214    215                     # Where no end datetime exists, use the start datetime as the   216                     # basis of any potential datetime specified if dt-control is   217                     # set.   218    219                     self.show_datetime_controls(is_start and period.get_form_start() or period.get_form_end(), is_start)   220    221                 elif name == "DTSTART":   222                     page.td(class_="objectvalue %s replaced" % field, rowspan=2)   223                     page.a("First occurrence replaced by a separate event", href=self.link_to(self.uid, replaced))   224                     page.td.close()   225    226                 page.tr.close()   227    228             # Handle the summary specially.   229    230             elif name == "SUMMARY":   231                 value = args.get("summary", [self.obj.get_value(name)])[0]   232    233                 page.td(class_="objectvalue summary")   234                 if self.is_organiser():   235                     self.control("summary", "text", value, size=80)   236                 else:   237                     page.add(value)   238                 page.td.close()   239                 page.tr.close()   240    241             # Handle attendees specially.   242    243             elif name == "ATTENDEE":   244                 attendee_map = dict(items)   245                 first = True   246    247                 for i, value in enumerate(attendees):   248                     if not first:   249                         page.tr()   250                     else:   251                         first = False   252    253                     # Obtain details of attendees to supply attributes.   254    255                     self.show_attendee(i, value, attendee_map.get(value))   256                     page.tr.close()   257    258                 # Allow more attendees to be specified.   259    260                 if self.is_organiser():   261                     if not first:   262                         page.tr()   263    264                     page.td()   265                     self.control("add", "submit", "add", id="add", class_="add")   266                     page.label("Add attendee", for_="add", class_="add")   267                     page.td.close()   268                     page.tr.close()   269    270             # Handle potentially many values of other kinds.   271    272             else:   273                 first = True   274    275                 for i, (value, attr) in enumerate(items):   276                     if not first:   277                         page.tr()   278                     else:   279                         first = False   280    281                     page.td(class_="objectvalue %s" % field)   282                     page.add(value)   283                     page.td.close()   284                     page.tr.close()   285    286         page.tbody.close()   287         page.table.close()   288    289         self.show_recurrences(errors)   290         self.show_conflicting_events()   291         self.show_request_controls()   292    293         page.form.close()   294    295     def show_attendee(self, i, attendee, attendee_attr):   296    297         """   298         For the current object, show the attendee in position 'i' with the given   299         'attendee' value, having 'attendee_attr' as any stored attributes.   300         """   301    302         page = self.page   303         args = self.env.get_args()   304    305         partstat = attendee_attr and attendee_attr.get("PARTSTAT")   306    307         page.td(class_="objectvalue")   308    309         # Show a form control as organiser for new attendees.   310    311         if self.is_organiser() and self.can_edit_attendee(attendee):   312             self.control("attendee", "value", attendee, size="40")   313         else:   314             self.control("attendee", "hidden", attendee)   315             page.add(attendee)   316         page.add(" ")   317    318         # Show participation status, editable for the current user.   319    320         if attendee == self.user:   321             self.menu("partstat", partstat, self.partstat_items, "partstat")   322    323         # Allow the participation indicator to act as a submit   324         # button in order to refresh the page and show a control for   325         # the current user, if indicated.   326    327         elif self.is_organiser() and self.attendee_is_new(attendee):   328             self.control("partstat-refresh", "submit", "refresh", id="partstat-%d" % i, class_="refresh")   329             page.label(dict(self.partstat_items).get(partstat, ""), for_="partstat-%s" % i, class_="partstat")   330    331         # Otherwise, just show a label with the participation status.   332    333         else:   334             page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat")   335    336         # Permit organisers to remove attendees.   337    338         if self.is_organiser():   339    340             # Permit the removal of newly-added attendees.   341    342             remove_type = self.can_remove_attendee(attendee) and "submit" or "checkbox"   343             self.control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove")   344    345             page.label("Remove", for_="remove-%d" % i, class_="remove")   346             page.label(for_="remove-%d" % i, class_="removed")   347             page.add("(Uninvited)")   348             page.span("Re-invite", class_="action")   349             page.label.close()   350    351         page.td.close()   352    353     def show_recurrences(self, errors=None):   354    355         """   356         Show recurrences for the current object. If 'errors' is given, show a   357         suitable message for the different errors provided.   358         """   359    360         page = self.page   361    362         # Obtain any parent object if this object is a specific recurrence.   363    364         if self.recurrenceid:   365             parent = self.get_stored_object(self.uid, None)   366             if not parent:   367                 return   368    369             page.p()   370             page.a("This event modifies a recurring event.", href=self.link_to(self.uid))   371             page.p.close()   372    373         # Obtain the periods associated with the event.   374         # NOTE: Add a control to add recurrences here.   375    376         recurrences = self.get_current_recurrences()   377    378         if len(recurrences) < 1:   379             return   380    381         recurrenceids = self._get_recurrences(self.uid)   382    383         page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())   384    385         # Show each recurrence in a separate table if editable.   386    387         if self.is_organiser() and recurrences:   388    389             for index, period in enumerate(recurrences):   390                 self.show_recurrence(index, period, self.recurrenceid, recurrenceids, errors)   391    392         # Otherwise, use a compact single table.   393    394         else:   395             page.table(cellspacing=5, cellpadding=5, class_="recurrence")   396             page.caption("Occurrences")   397             page.thead()   398             page.tr()   399             page.th("Start", class_="objectheading start")   400             page.th("End", class_="objectheading end")   401             page.tr.close()   402             page.thead.close()   403             page.tbody()   404    405             for index, period in enumerate(recurrences):   406                 page.tr()   407                 self.show_recurrence_label(period, self.recurrenceid, recurrenceids, True)   408                 self.show_recurrence_label(period, self.recurrenceid, recurrenceids, False)   409                 page.tr.close()   410    411             page.tbody.close()   412             page.table.close()   413    414     def show_recurrence(self, index, period, recurrenceid, recurrenceids, errors=None):   415    416         """   417         Show recurrence controls for a recurrence provided by the current object   418         with the given 'index' position in the list of periods, the given   419         'period' details, where a 'recurrenceid' indicates any specific   420         recurrence, and where 'recurrenceids' indicates all known additional   421         recurrences for the object.   422    423         If 'errors' is given, show a suitable message for the different errors   424         provided.   425         """   426    427         page = self.page   428         args = self.env.get_args()   429    430         p = event_period_from_period(period)   431         replaced = not recurrenceid and p.is_replaced(recurrenceids)   432    433         # Isolate the controls from neighbouring tables.   434    435         page.div()   436    437         self.show_object_datetime_controls(period, index)   438    439         page.table(cellspacing=5, cellpadding=5, class_="recurrence")   440         page.caption(period.origin == "RRULE" and "Occurrence from rule" or "Occurrence")   441         page.tbody()   442    443         page.tr()   444         error = errors and ("dtstart", index) in errors and " error" or ""   445         page.th("Start", class_="objectheading start%s" % error)   446         self.show_recurrence_controls(index, period, recurrenceid, recurrenceids, True)   447         page.tr.close()   448         page.tr()   449         error = errors and ("dtend", index) in errors and " error" or ""   450         page.th("End", class_="objectheading end%s" % error)   451         self.show_recurrence_controls(index, period, recurrenceid, recurrenceids, False)   452         page.tr.close()   453    454         # Permit the removal of recurrences.   455    456         if not replaced:   457             page.tr()   458             page.th("")   459             page.td()   460    461             remove_type = not self.obj.is_shared() or not period.origin and "submit" or "checkbox"   462             self.control("recur-remove", remove_type, str(index),   463                 str(index) in args.get("recur-remove", []),   464                 id="recur-remove-%d" % index, class_="remove")   465    466             page.label("Remove", for_="recur-remove-%d" % index, class_="remove")   467             page.label(for_="recur-remove-%d" % index, class_="removed")   468             page.add("(Removed)")   469             page.span("Re-add", class_="action")   470             page.label.close()   471    472             page.td.close()   473             page.tr.close()   474    475         page.tbody.close()   476         page.table.close()   477    478         page.div.close()   479    480     def show_conflicting_events(self):   481    482         "Show conflicting events for the current object."   483    484         page = self.page   485         recurrenceids = self._get_active_recurrences(self.uid)   486    487         # Obtain the user's timezone.   488    489         tzid = self.get_tzid()   490         periods = self.get_periods(self.obj)   491    492         # Indicate whether there are conflicting events.   493    494         conflicts = []   495         attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))   496    497         for participant in self.get_current_attendees():   498             if participant == self.user:   499                 freebusy = self.store.get_freebusy(participant)   500             else:   501                 freebusy = self.store.get_freebusy_for_other(self.user, participant)   502    503             if not freebusy:   504                 continue   505    506             # Obtain any time zone details from the suggested event.   507    508             _dtstart, attr = self.obj.get_item("DTSTART")   509             tzid = attr.get("TZID", tzid)   510    511             # Show any conflicts with periods of actual attendance.   512    513             participant_attr = attendee_map.get(participant)   514             partstat = participant_attr and participant_attr.get("PARTSTAT")   515             recurrences = self.obj.get_recurrence_start_points(recurrenceids, tzid)   516    517             for p in have_conflict(freebusy, periods, True):   518                 if not self.recurrenceid and p.is_replaced(recurrences):   519                     continue   520    521                 if ( # Unidentified or different event   522                      (p.uid != self.uid or self.recurrenceid and p.recurrenceid and p.recurrenceid != self.recurrenceid) and   523                      # Different period or unclear participation with the same period   524                      (p not in periods or not partstat in ("ACCEPTED", "TENTATIVE")) and   525                      # Participant not limited to organising   526                      p.transp != "ORG"   527                    ):   528    529                     conflicts.append(p)   530    531         conflicts.sort()   532    533         # Show any conflicts with periods of actual attendance.   534    535         if conflicts:   536             page.p("This event conflicts with others:")   537    538             page.table(cellspacing=5, cellpadding=5, class_="conflicts")   539             page.thead()   540             page.tr()   541             page.th("Event")   542             page.th("Start")   543             page.th("End")   544             page.tr.close()   545             page.thead.close()   546             page.tbody()   547    548             for p in conflicts:   549    550                 # Provide details of any conflicting event.   551    552                 start = self.format_datetime(to_timezone(p.get_start(), tzid), "long")   553                 end = self.format_datetime(to_timezone(p.get_end(), tzid), "long")   554    555                 page.tr()   556    557                 # Show the event summary for the conflicting event.   558    559                 page.td()   560                 if p.summary:   561                     page.a(p.summary, href=self.link_to(p.uid, p.recurrenceid))   562                 else:   563                     page.add("(Unspecified event)")   564                 page.td.close()   565    566                 page.td(start)   567                 page.td(end)   568    569                 page.tr.close()   570    571             page.tbody.close()   572             page.table.close()   573    574 class EventPage(EventPageFragment):   575    576     "A request handler for the event page."   577    578     def __init__(self, resource=None, messenger=None):   579         ResourceClientForObject.__init__(self, resource)   580         self.messenger = messenger or Messenger()   581    582     # Request logic methods.   583    584     def is_initial_load(self):   585    586         "Return whether the event is being loaded and shown for the first time."   587    588         return not self.env.get_args().has_key("editing")   589    590     def handle_request(self):   591    592         """   593         Handle actions involving the current object, returning an error if one   594         occurred, or None if the request was successfully handled.   595         """   596    597         # Handle a submitted form.   598    599         args = self.env.get_args()   600    601         # Get the possible actions.   602    603         reply = args.has_key("reply")   604         discard = args.has_key("discard")   605         create = args.has_key("create")   606         cancel = args.has_key("cancel")   607         ignore = args.has_key("ignore")   608         save = args.has_key("save")   609    610         have_action = reply or discard or create or cancel or ignore or save   611    612         if not have_action:   613             return ["action"]   614    615         # If ignoring the object, return to the calendar.   616    617         if ignore:   618             self.redirect(self.env.get_path())   619             return None   620    621         # Update the object.   622    623         single_user = False   624    625         if reply or create or cancel or save:   626    627             # Update principal event details if organiser.   628    629             if self.is_organiser():   630    631                 # Update time periods (main and recurring).   632    633                 try:   634                     period = self.handle_main_period()   635                 except PeriodError, exc:   636                     return exc.args   637    638                 try:   639                     periods = self.handle_recurrence_periods()   640                 except PeriodError, exc:   641                     return exc.args   642    643                 # Set the periods in the object, first obtaining removed and   644                 # modified period information.   645    646                 to_unschedule = self.get_removed_periods()   647    648                 self.obj.set_period(period)   649                 self.obj.set_periods(periods)   650    651                 # Update summary.   652    653                 if args.has_key("summary"):   654                     self.obj["SUMMARY"] = [(args["summary"][0], {})]   655    656                 # Obtain any participants and those to be removed.   657    658                 attendees = self.get_attendees_from_page()   659                 removed = [attendees[int(i)] for i in args.get("remove", [])]   660                 to_cancel = self.update_attendees(self.obj, attendees, removed)   661                 single_user = not attendees or attendees == [self.user]   662    663             # Update attendee participation for the current user.   664    665             if args.has_key("partstat"):   666                 self.update_participation(self.obj, args["partstat"][0])   667    668         # Process any action.   669    670         invite = not save and create and not single_user   671         save = save or create and single_user   672    673         handled = True   674    675         if reply or invite or cancel:   676    677             client = ManagerClient(self.obj, self.user, self.messenger)   678    679             # Process the object and remove it from the list of requests.   680    681             if reply and client.process_received_request():   682                 self.remove_request(self.uid, self.recurrenceid)   683    684             elif self.is_organiser() and (invite or cancel):   685    686                 # Invitation, uninvitation and unscheduling...   687    688                 if client.process_created_request(   689                     invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule):   690    691                     self.remove_request(self.uid, self.recurrenceid)   692    693         # Save single user events.   694    695         elif save:   696             self.store.set_event(self.user, self.uid, self.recurrenceid, node=self.obj.to_node())   697             self.update_event_in_freebusy()   698             self.remove_request(self.uid, self.recurrenceid)   699    700         # Remove the request and the object.   701    702         elif discard:   703             self.remove_event_from_freebusy()   704             self.remove_event(self.uid, self.recurrenceid)   705             self.remove_request(self.uid, self.recurrenceid)   706    707         else:   708             handled = False   709    710         # Upon handling an action, redirect to the main page.   711    712         if handled:   713             self.redirect(self.env.get_path())   714    715         return None   716    717     def handle_main_period(self):   718    719         "Return period details for the main start/end period in an event."   720    721         return self.get_main_period_from_page().as_event_period()   722    723     def handle_recurrence_periods(self):   724    725         "Return period details for the recurrences specified for an event."   726    727         return [p.as_event_period(i) for i, p in enumerate(self.get_recurrences_from_page())]   728    729     # Access to form-originating object information.   730    731     def get_main_period_from_page(self):   732    733         "Return the main period defined in the event form."   734    735         args = self.env.get_args()   736    737         dtend_enabled = args.get("dtend-control", [None])[0]   738         dttimes_enabled = args.get("dttimes-control", [None])[0]   739         start = self.get_date_control_values("dtstart")   740         end = self.get_date_control_values("dtend")   741    742         return FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid())   743    744     def get_recurrences_from_page(self):   745    746         "Return the recurrences defined in the event form."   747    748         args = self.env.get_args()   749    750         all_dtend_enabled = args.get("dtend-control-recur", [])   751         all_dttimes_enabled = args.get("dttimes-control-recur", [])   752         all_starts = self.get_date_control_values("dtstart-recur", multiple=True)   753         all_ends = self.get_date_control_values("dtend-recur", multiple=True, tzid_name="dtstart-recur")   754         all_origins = args.get("recur-origin", [])   755    756         periods = []   757    758         for index, (start, end, dtend_enabled, dttimes_enabled, origin) in \   759             enumerate(map(None, all_starts, all_ends, all_dtend_enabled, all_dttimes_enabled, all_origins)):   760    761             dtend_enabled = str(index) in all_dtend_enabled   762             dttimes_enabled = str(index) in all_dttimes_enabled   763             period = FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid(), origin)   764             periods.append(period)   765    766         return periods   767    768     def get_removed_periods(self):   769    770         "Return a list of recurrence periods to remove upon updating an event."   771    772         to_unschedule = []   773         args = self.env.get_args()   774         for i in args.get("recur-remove", []):   775             to_unschedule.append(periods[int(i)])   776         return to_unschedule   777    778     def get_attendees_from_page(self):   779    780         """   781         Return attendees from the request, normalised for iCalendar purposes,   782         and without duplicates.   783         """   784    785         args = self.env.get_args()   786    787         attendees = args.get("attendee", [])   788         unique_attendees = set()   789         ordered_attendees = []   790    791         for attendee in attendees:   792             if not attendee.strip():   793                 continue   794             attendee = get_uri(attendee)   795             if attendee not in unique_attendees:   796                 unique_attendees.add(attendee)   797                 ordered_attendees.append(attendee)   798    799         return ordered_attendees   800    801     def update_attendees_from_page(self):   802    803         "Add or remove attendees. This does not affect the stored object."   804    805         args = self.env.get_args()   806    807         attendees = self.get_attendees_from_page()   808    809         if args.has_key("add"):   810             attendees.append("")   811    812         # Only actually remove attendees if the event is unsent, if the attendee   813         # is new, or if it is the current user being removed.   814    815         if args.has_key("remove"):   816             still_to_remove = []   817    818             for i in args["remove"]:   819                 try:   820                     attendee = attendees[int(i)]   821                 except IndexError:   822                     continue   823    824                 if self.can_remove_attendee(attendee):   825                     attendees.remove(attendee)   826                 else:   827                     still_to_remove.append(i)   828    829             args["remove"] = still_to_remove   830    831         return attendees   832    833     # Access to current object information.   834    835     def get_current_main_period(self):   836    837         """   838         Return the currently active main period for the current object depending   839         on whether editing has begun or whether the object has just been loaded.   840         """   841    842         if self.is_initial_load() or not self.is_organiser():   843             return self.get_stored_main_period()   844         else:   845             return self.get_main_period_from_page()   846    847     def get_current_recurrences(self):   848    849         """   850         Return recurrences for the current object using the original object   851         details where no editing is in progress, using form data otherwise.   852         """   853    854         if self.is_initial_load() or not self.is_organiser():   855             return self.get_stored_recurrences()   856         else:   857             return self.get_recurrences_from_page()   858    859     def get_current_attendees(self):   860    861         """   862         Return attendees for the current object depending on whether the object   863         has been edited or instead provides such information from its stored   864         form.   865         """   866    867         if self.is_initial_load() or not self.is_organiser():   868             return self.get_stored_attendees()   869         else:   870             return self.get_attendees_from_page()   871    872     def update_current_attendees(self):   873    874         "Return an updated collection of attendees for the current object."   875    876         if self.is_initial_load() or not self.is_organiser():   877             return self.get_stored_attendees()   878         else:   879             return self.update_attendees_from_page()   880    881     # Full page output methods.   882    883     def show(self, path_info):   884    885         "Show an object request using the given 'path_info' for the current user."   886    887         uid, recurrenceid = self.get_identifiers(path_info)   888         obj = self.get_stored_object(uid, recurrenceid)   889         self.set_object(obj)   890    891         if not obj:   892             return False   893    894         errors = self.handle_request()   895    896         if not errors:   897             return True   898    899         self.new_page(title="Event")   900         self.show_object_on_page(errors)   901    902         return True   903    904 # vim: tabstop=4 expandtab shiftwidth=4