# HG changeset patch # User Paul Boddie # Date 1443130907 -7200 # Node ID f2fa0b8bbcaffec6daebbefe18e8635ea0b0e4c3 # Parent a92517218628c05b4d8b406dd100166812f2cc51 Made a separate event page fragment class, separating request handling for the event page from the general presentation of events. diff -r a92517218628 -r f2fa0b8bbcaf imipweb/event.py --- a/imipweb/event.py Thu Sep 24 20:41:58 2015 +0200 +++ b/imipweb/event.py Thu Sep 24 23:41:47 2015 +0200 @@ -23,19 +23,16 @@ from imiptools.dates import to_timezone from imiptools.mail import Messenger from imiptools.period import have_conflict -from imipweb.data import EventPeriod, \ - event_period_from_period, form_period_from_period, \ - FormDate, FormPeriod, PeriodError +from imipweb.data import EventPeriod, event_period_from_period, FormPeriod, PeriodError from imipweb.client import ManagerClient -from imipweb.resource import FormUtilities, ResourceClientForObject +from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject -class EventPage(ResourceClientForObject, FormUtilities): - - "A request handler for the event page." +class EventPageFragment(ResourceClientForObject, DateTimeFormUtilities, FormUtilities): - def __init__(self, resource=None, messenger=None): + "A resource presenting the details of an event." + + def __init__(self, resource=None): ResourceClientForObject.__init__(self, resource) - self.messenger = messenger or Messenger() # Various property values and labels. @@ -56,323 +53,9 @@ (None, "Not indicated"), ] - # Access to stored object information. - def is_organiser(self): return get_uri(self.obj.get_value("ORGANIZER")) == self.user - def get_stored_attendees(self): - return uri_values(self.obj.get_values("ATTENDEE") or []) - - def get_stored_main_period(self): - - "Return the main event period for the current object." - - (dtstart, dtstart_attr), (dtend, dtend_attr) = self.obj.get_main_period_items(self.get_tzid()) - return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr) - - def get_stored_recurrences(self): - - "Return recurrences computed using the current object." - - recurrences = [] - for period in self.get_periods(self.obj): - if period.origin != "DTSTART": - recurrences.append(period) - return recurrences - - # Request logic methods. - - def is_initial_load(self): - - "Return whether the event is being loaded and shown for the first time." - - return not self.env.get_args().has_key("editing") - - def handle_request(self): - - """ - Handle actions involving the current object, returning an error if one - occurred, or None if the request was successfully handled. - """ - - # Handle a submitted form. - - args = self.env.get_args() - - # Get the possible actions. - - reply = args.has_key("reply") - discard = args.has_key("discard") - create = args.has_key("create") - cancel = args.has_key("cancel") - ignore = args.has_key("ignore") - save = args.has_key("save") - - have_action = reply or discard or create or cancel or ignore or save - - if not have_action: - return ["action"] - - # If ignoring the object, return to the calendar. - - if ignore: - self.redirect(self.env.get_path()) - return None - - # Update the object. - - single_user = False - - if reply or create or cancel or save: - - # Update principal event details if organiser. - - if self.is_organiser(): - - # Update time periods (main and recurring). - - try: - period = self.handle_main_period() - except PeriodError, exc: - return exc.args - - try: - periods = self.handle_recurrence_periods() - except PeriodError, exc: - return exc.args - - # Set the periods in the object, first obtaining removed and - # modified period information. - - to_unschedule = self.get_removed_periods() - - self.obj.set_period(period) - self.obj.set_periods(periods) - - # Update summary. - - if args.has_key("summary"): - self.obj["SUMMARY"] = [(args["summary"][0], {})] - - # Obtain any participants and those to be removed. - - attendees = self.get_attendees_from_page() - removed = [attendees[int(i)] for i in args.get("remove", [])] - to_cancel = self.update_attendees(self.obj, attendees, removed) - single_user = not attendees or attendees == [self.user] - - # Update attendee participation for the current user. - - if args.has_key("partstat"): - self.update_participation(self.obj, args["partstat"][0]) - - # Process any action. - - invite = not save and create and not single_user - save = save or create and single_user - - handled = True - - if reply or invite or cancel: - - client = ManagerClient(self.obj, self.user, self.messenger) - - # Process the object and remove it from the list of requests. - - if reply and client.process_received_request(): - self.remove_request(self.uid, self.recurrenceid) - - elif self.is_organiser() and (invite or cancel): - - # Invitation, uninvitation and unscheduling... - - if client.process_created_request( - invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule): - - self.remove_request(self.uid, self.recurrenceid) - - # Save single user events. - - elif save: - self.store.set_event(self.user, self.uid, self.recurrenceid, node=self.obj.to_node()) - self.update_event_in_freebusy() - self.remove_request(self.uid, self.recurrenceid) - - # Remove the request and the object. - - elif discard: - self.remove_event_from_freebusy() - self.remove_event(self.uid, self.recurrenceid) - self.remove_request(self.uid, self.recurrenceid) - - else: - handled = False - - # Upon handling an action, redirect to the main page. - - if handled: - self.redirect(self.env.get_path()) - - return None - - def handle_main_period(self): - - "Return period details for the main start/end period in an event." - - return self.get_main_period().as_event_period() - - def handle_recurrence_periods(self): - - "Return period details for the recurrences specified for an event." - - return [p.as_event_period(i) for i, p in enumerate(self.get_recurrences())] - - def get_date_control_values(self, name, multiple=False, tzid_name=None): - - """ - Return a dictionary containing date, time and tzid entries for fields - starting with 'name'. If 'multiple' is set to a true value, many - dictionaries will be returned corresponding to a collection of - datetimes. If 'tzid_name' is specified, the time zone information will - be acquired from a field starting with 'tzid_name' instead of 'name'. - """ - - args = self.env.get_args() - - dates = args.get("%s-date" % name, []) - hours = args.get("%s-hour" % name, []) - minutes = args.get("%s-minute" % name, []) - seconds = args.get("%s-second" % name, []) - tzids = args.get("%s-tzid" % (tzid_name or name), []) - - # Handle absent values by employing None values. - - field_values = map(None, dates, hours, minutes, seconds, tzids) - - if not field_values and not multiple: - all_values = FormDate() - else: - all_values = [] - for date, hour, minute, second, tzid in field_values: - value = FormDate(date, hour, minute, second, tzid or self.get_tzid()) - - # Return a single value or append to a collection of all values. - - if not multiple: - return value - else: - all_values.append(value) - - return all_values - - def get_current_main_period(self): - - """ - Return the currently active main period for the current object depending - on whether editing has begun or whether the object has just been loaded. - """ - - if self.is_initial_load() or not self.is_organiser(): - return self.get_stored_main_period() - else: - return self.get_main_period() - - def get_main_period(self): - - "Return the main period defined in the event form." - - args = self.env.get_args() - - dtend_enabled = args.get("dtend-control", [None])[0] - dttimes_enabled = args.get("dttimes-control", [None])[0] - start = self.get_date_control_values("dtstart") - end = self.get_date_control_values("dtend") - - return FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid()) - - def get_current_recurrences(self): - - """ - Return recurrences for the current object using the original object - details where no editing is in progress, using form data otherwise. - """ - - if self.is_initial_load() or not self.is_organiser(): - return self.get_stored_recurrences() - else: - return self.get_recurrences() - - def get_recurrences(self): - - "Return the recurrences defined in the event form." - - args = self.env.get_args() - - all_dtend_enabled = args.get("dtend-control-recur", []) - all_dttimes_enabled = args.get("dttimes-control-recur", []) - all_starts = self.get_date_control_values("dtstart-recur", multiple=True) - all_ends = self.get_date_control_values("dtend-recur", multiple=True, tzid_name="dtstart-recur") - all_origins = args.get("recur-origin", []) - - periods = [] - - for index, (start, end, dtend_enabled, dttimes_enabled, origin) in \ - enumerate(map(None, all_starts, all_ends, all_dtend_enabled, all_dttimes_enabled, all_origins)): - - dtend_enabled = str(index) in all_dtend_enabled - dttimes_enabled = str(index) in all_dttimes_enabled - period = FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid(), origin) - periods.append(period) - - return periods - - def get_removed_periods(self): - - "Return a list of recurrence periods to remove upon updating an event." - - to_unschedule = [] - args = self.env.get_args() - for i in args.get("recur-remove", []): - to_unschedule.append(periods[int(i)]) - return to_unschedule - - def get_current_attendees(self): - - """ - Return attendees for the current object depending on whether the object - has been edited or instead provides such information from its stored - form. - """ - - if self.is_initial_load() or not self.is_organiser(): - return self.get_stored_attendees() - else: - return self.get_attendees_from_page() - - def get_attendees_from_page(self): - - """ - Return attendees from the request, normalised for iCalendar purposes, - and without duplicates. - """ - - args = self.env.get_args() - - attendees = args.get("attendee", []) - unique_attendees = set() - ordered_attendees = [] - - for attendee in attendees: - if not attendee.strip(): - continue - attendee = get_uri(attendee) - if attendee not in unique_attendees: - unique_attendees.add(attendee) - ordered_attendees.append(attendee) - - return ordered_attendees - def can_remove_attendee(self, attendee): """ @@ -394,37 +77,38 @@ return attendee not in self.get_stored_attendees() - def update_attendees_from_page(self): + # Access to stored object information. - "Add or remove attendees. This does not affect the stored object." + def get_stored_attendees(self): + return uri_values(self.obj.get_values("ATTENDEE") or []) - args = self.env.get_args() + def get_stored_main_period(self): - attendees = self.get_attendees_from_page() + "Return the main event period for the current object." - if args.has_key("add"): - attendees.append("") + (dtstart, dtstart_attr), (dtend, dtend_attr) = self.obj.get_main_period_items(self.get_tzid()) + return EventPeriod(dtstart, dtend, self.get_tzid(), None, dtstart_attr, dtend_attr) - # Only actually remove attendees if the event is unsent, if the attendee - # is new, or if it is the current user being removed. + def get_stored_recurrences(self): + + "Return recurrences computed using the current object." - if args.has_key("remove"): - still_to_remove = [] + recurrences = [] + for period in self.get_periods(self.obj): + if period.origin != "DTSTART": + recurrences.append(period) + return recurrences - for i in args["remove"]: - try: - attendee = attendees[int(i)] - except IndexError: - continue + # Access to current object information. - if self.can_remove_attendee(attendee): - attendees.remove(attendee) - else: - still_to_remove.append(i) + def get_current_main_period(self): + return self.get_stored_main_period() - args["remove"] = still_to_remove + def get_current_recurrences(self): + return self.get_stored_recurrences() - return attendees + def get_current_attendees(self): + return self.get_stored_attendees() # Page fragment methods. @@ -486,18 +170,14 @@ # Obtain basic event information, generating any necessary editing controls. - if self.is_initial_load() or not self.is_organiser(): - attendees = self.get_stored_attendees() - else: - attendees = self.update_attendees_from_page() - - p = self.get_current_main_period() - self.show_object_datetime_controls(p) + attendees = self.update_current_attendees() + period = self.get_current_main_period() + self.show_object_datetime_controls(period) # Obtain any separate recurrences for this event. recurrenceids = self._get_active_recurrences(self.uid) - replaced = not self.recurrenceid and p.is_replaced(recurrenceids) + replaced = not self.recurrenceid and period.is_replaced(recurrenceids) # Provide a summary of the object. @@ -536,7 +216,7 @@ # basis of any potential datetime specified if dt-control is # set. - self.show_datetime_controls(is_start and p.get_form_start() or p.get_form_end(), is_start) + self.show_datetime_controls(is_start and period.get_form_start() or period.get_form_end(), is_start) elif name == "DTSTART": page.td(class_="objectvalue %s replaced" % field, rowspan=2) @@ -891,188 +571,312 @@ page.tbody.close() page.table.close() - # Generation of controls within page fragments. +class EventPage(EventPageFragment): + + "A request handler for the event page." + + def __init__(self, resource=None, messenger=None): + ResourceClientForObject.__init__(self, resource) + self.messenger = messenger or Messenger() - def show_object_datetime_controls(self, period, index=None): + # Request logic methods. + + def is_initial_load(self): + + "Return whether the event is being loaded and shown for the first time." + + return not self.env.get_args().has_key("editing") + + def handle_request(self): """ - Show datetime-related controls if already active or if an object needs - them for the given 'period'. The given 'index' is used to parameterise - individual controls for dynamic manipulation. + Handle actions involving the current object, returning an error if one + occurred, or None if the request was successfully handled. """ - p = form_period_from_period(period) + # Handle a submitted form. - page = self.page args = self.env.get_args() - _id = self.element_identifier - _name = self.element_name - _enable = self.element_enable + + # Get the possible actions. + + reply = args.has_key("reply") + discard = args.has_key("discard") + create = args.has_key("create") + cancel = args.has_key("cancel") + ignore = args.has_key("ignore") + save = args.has_key("save") - # Add a dynamic stylesheet to permit the controls to modify the display. - # NOTE: The style details need to be coordinated with the static - # NOTE: stylesheet. + have_action = reply or discard or create or cancel or ignore or save + + if not have_action: + return ["action"] - if index is not None: - page.style(type="text/css") + # If ignoring the object, return to the calendar. - # Unlike the rules for object properties, these affect recurrence - # properties. + if ignore: + self.redirect(self.env.get_path()) + return None + + # Update the object. - page.add("""\ -input#dttimes-enable-%(index)d, -input#dtend-enable-%(index)d, -input#dttimes-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue .time.enabled, -input#dttimes-enable-%(index)d:checked ~ .recurrence td.objectvalue .time.disabled, -input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .dt.enabled, -input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .dt.disabled { - display: none; -}""" % {"index" : index}) + single_user = False + + if reply or create or cancel or save: + + # Update principal event details if organiser. + + if self.is_organiser(): + + # Update time periods (main and recurring). - page.style.close() + try: + period = self.handle_main_period() + except PeriodError, exc: + return exc.args - self.control( - _name("dtend-control", "recur", index), "checkbox", - _enable(index), p.end_enabled, - id=_id("dtend-enable", index) - ) + try: + periods = self.handle_recurrence_periods() + except PeriodError, exc: + return exc.args + + # Set the periods in the object, first obtaining removed and + # modified period information. + + to_unschedule = self.get_removed_periods() + + self.obj.set_period(period) + self.obj.set_periods(periods) - self.control( - _name("dttimes-control", "recur", index), "checkbox", - _enable(index), p.times_enabled, - id=_id("dttimes-enable", index) - ) + # Update summary. + + if args.has_key("summary"): + self.obj["SUMMARY"] = [(args["summary"][0], {})] + + # Obtain any participants and those to be removed. - def show_datetime_controls(self, formdate, show_start): + attendees = self.get_attendees_from_page() + removed = [attendees[int(i)] for i in args.get("remove", [])] + to_cancel = self.update_attendees(self.obj, attendees, removed) + single_user = not attendees or attendees == [self.user] + + # Update attendee participation for the current user. - """ - Show datetime details from the current object for the 'formdate', - showing start details if 'show_start' is set to a true value. Details - will appear as controls for organisers and labels for attendees. - """ + if args.has_key("partstat"): + self.update_participation(self.obj, args["partstat"][0]) + + # Process any action. - page = self.page + invite = not save and create and not single_user + save = save or create and single_user - # Show controls for editing as organiser. + handled = True - if self.is_organiser(): - page.td(class_="objectvalue dt%s" % (show_start and "start" or "end")) + if reply or invite or cancel: + + client = ManagerClient(self.obj, self.user, self.messenger) - if show_start: - page.div(class_="dt enabled") - self.date_controls("dtstart", formdate) - page.br() - page.label("Specify times", for_="dttimes-enable", class_="time disabled enable") - page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable") - page.div.close() + # Process the object and remove it from the list of requests. + + if reply and client.process_received_request(): + self.remove_request(self.uid, self.recurrenceid) + + elif self.is_organiser() and (invite or cancel): + + # Invitation, uninvitation and unscheduling... + + if client.process_created_request( + invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule): + + self.remove_request(self.uid, self.recurrenceid) - else: - page.div(class_="dt disabled") - page.label("Specify end date", for_="dtend-enable", class_="enable") - page.div.close() - page.div(class_="dt enabled") - self.date_controls("dtend", formdate) - page.br() - page.label("End on same day", for_="dtend-enable", class_="disable") - page.div.close() + # Save single user events. + + elif save: + self.store.set_event(self.user, self.uid, self.recurrenceid, node=self.obj.to_node()) + self.update_event_in_freebusy() + self.remove_request(self.uid, self.recurrenceid) - page.td.close() + # Remove the request and the object. - # Show a label as attendee. + elif discard: + self.remove_event_from_freebusy() + self.remove_event(self.uid, self.recurrenceid) + self.remove_request(self.uid, self.recurrenceid) else: - dt = formdate.as_datetime() - if dt: - page.td(self.format_datetime(dt, "full")) - else: - page.td("(Unrecognised date)") + handled = False + + # Upon handling an action, redirect to the main page. + + if handled: + self.redirect(self.env.get_path()) + + return None + + def handle_main_period(self): + + "Return period details for the main start/end period in an event." + + return self.get_main_period_from_page().as_event_period() + + def handle_recurrence_periods(self): + + "Return period details for the recurrences specified for an event." + + return [p.as_event_period(i) for i, p in enumerate(self.get_recurrences_from_page())] + + # Access to form-originating object information. + + def get_main_period_from_page(self): + + "Return the main period defined in the event form." + + args = self.env.get_args() + + dtend_enabled = args.get("dtend-control", [None])[0] + dttimes_enabled = args.get("dttimes-control", [None])[0] + start = self.get_date_control_values("dtstart") + end = self.get_date_control_values("dtend") + + return FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid()) - def show_recurrence_controls(self, index, period, recurrenceid, recurrenceids, show_start): + def get_recurrences_from_page(self): + + "Return the recurrences defined in the event form." + + args = self.env.get_args() + + all_dtend_enabled = args.get("dtend-control-recur", []) + all_dttimes_enabled = args.get("dttimes-control-recur", []) + all_starts = self.get_date_control_values("dtstart-recur", multiple=True) + all_ends = self.get_date_control_values("dtend-recur", multiple=True, tzid_name="dtstart-recur") + all_origins = args.get("recur-origin", []) + + periods = [] + + for index, (start, end, dtend_enabled, dttimes_enabled, origin) in \ + enumerate(map(None, all_starts, all_ends, all_dtend_enabled, all_dttimes_enabled, all_origins)): + + dtend_enabled = str(index) in all_dtend_enabled + dttimes_enabled = str(index) in all_dttimes_enabled + period = FormPeriod(start, end, dtend_enabled, dttimes_enabled, self.get_tzid(), origin) + periods.append(period) + + return periods + + def get_removed_periods(self): + + "Return a list of recurrence periods to remove upon updating an event." + + to_unschedule = [] + args = self.env.get_args() + for i in args.get("recur-remove", []): + to_unschedule.append(periods[int(i)]) + return to_unschedule + + def get_attendees_from_page(self): """ - Show datetime details from the current object for the recurrence having - the given 'index', with the recurrence period described by 'period', - indicating a start, end and origin of the period from the event details, - employing any 'recurrenceid' and 'recurrenceids' for the object to - configure the displayed information. - - If 'show_start' is set to a true value, the start details will be shown; - otherwise, the end details will be shown. + Return attendees from the request, normalised for iCalendar purposes, + and without duplicates. """ - page = self.page - _id = self.element_identifier - _name = self.element_name + args = self.env.get_args() - p = event_period_from_period(period) - replaced = not recurrenceid and p.is_replaced(recurrenceids) - - # Show controls for editing as organiser. + attendees = args.get("attendee", []) + unique_attendees = set() + ordered_attendees = [] - if self.is_organiser() and not replaced: - page.td(class_="objectvalue dt%s" % (show_start and "start" or "end")) - - read_only = period.origin == "RRULE" + for attendee in attendees: + if not attendee.strip(): + continue + attendee = get_uri(attendee) + if attendee not in unique_attendees: + unique_attendees.add(attendee) + ordered_attendees.append(attendee) - if show_start: - page.div(class_="dt enabled") - self.date_controls(_name("dtstart", "recur", index), p.get_form_start(), index=index, read_only=read_only) - if not read_only: - page.br() - page.label("Specify times", for_=_id("dttimes-enable", index), class_="time disabled enable") - page.label("Specify dates only", for_=_id("dttimes-enable", index), class_="time enabled disable") - page.div.close() + return ordered_attendees + + def update_attendees_from_page(self): + + "Add or remove attendees. This does not affect the stored object." + + args = self.env.get_args() + + attendees = self.get_attendees_from_page() - # Put the origin somewhere. + if args.has_key("add"): + attendees.append("") - self.control("recur-origin", "hidden", p.origin or "") + # Only actually remove attendees if the event is unsent, if the attendee + # is new, or if it is the current user being removed. + + if args.has_key("remove"): + still_to_remove = [] - else: - page.div(class_="dt disabled") - if not read_only: - page.label("Specify end date", for_=_id("dtend-enable", index), class_="enable") - page.div.close() - page.div(class_="dt enabled") - self.date_controls(_name("dtend", "recur", index), p.get_form_end(), index=index, show_tzid=False, read_only=read_only) - if not read_only: - page.br() - page.label("End on same day", for_=_id("dtend-enable", index), class_="disable") - page.div.close() + for i in args["remove"]: + try: + attendee = attendees[int(i)] + except IndexError: + continue - page.td.close() - - # Show label as attendee. + if self.can_remove_attendee(attendee): + attendees.remove(attendee) + else: + still_to_remove.append(i) - else: - self.show_recurrence_label(p, recurrenceid, recurrenceids, show_start) + args["remove"] = still_to_remove + + return attendees - def show_recurrence_label(self, period, recurrenceid, recurrenceids, show_start): + # Access to current object information. + + def get_current_main_period(self): """ - Show datetime details for the given 'period', employing any - 'recurrenceid' and 'recurrenceids' for the object to configure the - displayed information. + Return the currently active main period for the current object depending + on whether editing has begun or whether the object has just been loaded. + """ - If 'show_start' is set to a true value, the start details will be shown; - otherwise, the end details will be shown. + if self.is_initial_load() or not self.is_organiser(): + return self.get_stored_main_period() + else: + return self.get_main_period_from_page() + + def get_current_recurrences(self): + + """ + Return recurrences for the current object using the original object + details where no editing is in progress, using form data otherwise. """ - page = self.page + if self.is_initial_load() or not self.is_organiser(): + return self.get_stored_recurrences() + else: + return self.get_recurrences_from_page() - p = event_period_from_period(period) - replaced = not recurrenceid and p.is_replaced(recurrenceids) + def get_current_attendees(self): + + """ + Return attendees for the current object depending on whether the object + has been edited or instead provides such information from its stored + form. + """ - css = " ".join([ - replaced and "replaced" or "", - p.is_affected(recurrenceid) and "affected" or "" - ]) + if self.is_initial_load() or not self.is_organiser(): + return self.get_stored_attendees() + else: + return self.get_attendees_from_page() + + def update_current_attendees(self): - formdate = show_start and p.get_form_start() or p.get_form_end() - dt = formdate.as_datetime() - if dt: - page.td(self.format_datetime(dt, "long"), class_=css) + "Return an updated collection of attendees for the current object." + + if self.is_initial_load() or not self.is_organiser(): + return self.get_stored_attendees() else: - page.td("(Unrecognised date)") + return self.update_attendees_from_page() # Full page output methods. diff -r a92517218628 -r f2fa0b8bbcaf imipweb/resource.py --- a/imipweb/resource.py Thu Sep 24 20:41:58 2015 +0200 +++ b/imipweb/resource.py Thu Sep 24 23:41:47 2015 +0200 @@ -24,6 +24,7 @@ from imiptools.data import get_uri, uri_values from imiptools.dates import format_datetime, get_recurrence_start_point, to_date from imiptools.period import remove_period, remove_affected_period +from imipweb.data import event_period_from_period, form_period_from_period, FormDate from imipweb.env import CGIEnvironment import babel.dates import imip_store @@ -215,7 +216,7 @@ class FormUtilities: - "Utility methods." + "Utility methods resource mix-in." def control(self, name, type, value, selected=False, **kw): @@ -341,4 +342,227 @@ entries = [(tzid, tzid) for tzid in pytz.all_timezones] self.menu(name, default, entries, index=index) +class DateTimeFormUtilities: + + "Date/time control methods resource mix-in." + + def show_object_datetime_controls(self, period, index=None): + + """ + Show datetime-related controls if already active or if an object needs + them for the given 'period'. The given 'index' is used to parameterise + individual controls for dynamic manipulation. + """ + + p = form_period_from_period(period) + + page = self.page + args = self.env.get_args() + _id = self.element_identifier + _name = self.element_name + _enable = self.element_enable + + # Add a dynamic stylesheet to permit the controls to modify the display. + # NOTE: The style details need to be coordinated with the static + # NOTE: stylesheet. + + if index is not None: + page.style(type="text/css") + + # Unlike the rules for object properties, these affect recurrence + # properties. + + page.add("""\ +input#dttimes-enable-%(index)d, +input#dtend-enable-%(index)d, +input#dttimes-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue .time.enabled, +input#dttimes-enable-%(index)d:checked ~ .recurrence td.objectvalue .time.disabled, +input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .dt.enabled, +input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .dt.disabled { + display: none; +}""" % {"index" : index}) + + page.style.close() + + self.control( + _name("dtend-control", "recur", index), "checkbox", + _enable(index), p.end_enabled, + id=_id("dtend-enable", index) + ) + + self.control( + _name("dttimes-control", "recur", index), "checkbox", + _enable(index), p.times_enabled, + id=_id("dttimes-enable", index) + ) + + def show_datetime_controls(self, formdate, show_start): + + """ + Show datetime details from the current object for the 'formdate', + showing start details if 'show_start' is set to a true value. Details + will appear as controls for organisers and labels for attendees. + """ + + page = self.page + + # Show controls for editing as organiser. + + if self.is_organiser(): + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end")) + + if show_start: + page.div(class_="dt enabled") + self.date_controls("dtstart", formdate) + page.br() + page.label("Specify times", for_="dttimes-enable", class_="time disabled enable") + page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable") + page.div.close() + + else: + page.div(class_="dt disabled") + page.label("Specify end date", for_="dtend-enable", class_="enable") + page.div.close() + page.div(class_="dt enabled") + self.date_controls("dtend", formdate) + page.br() + page.label("End on same day", for_="dtend-enable", class_="disable") + page.div.close() + + page.td.close() + + # Show a label as attendee. + + else: + dt = formdate.as_datetime() + if dt: + page.td(self.format_datetime(dt, "full")) + else: + page.td("(Unrecognised date)") + + def show_recurrence_controls(self, index, period, recurrenceid, recurrenceids, show_start): + + """ + Show datetime details from the current object for the recurrence having + the given 'index', with the recurrence period described by 'period', + indicating a start, end and origin of the period from the event details, + employing any 'recurrenceid' and 'recurrenceids' for the object to + configure the displayed information. + + If 'show_start' is set to a true value, the start details will be shown; + otherwise, the end details will be shown. + """ + + page = self.page + _id = self.element_identifier + _name = self.element_name + + p = event_period_from_period(period) + replaced = not recurrenceid and p.is_replaced(recurrenceids) + + # Show controls for editing as organiser. + + if self.is_organiser() and not replaced: + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end")) + + read_only = period.origin == "RRULE" + + if show_start: + page.div(class_="dt enabled") + self.date_controls(_name("dtstart", "recur", index), p.get_form_start(), index=index, read_only=read_only) + if not read_only: + page.br() + page.label("Specify times", for_=_id("dttimes-enable", index), class_="time disabled enable") + page.label("Specify dates only", for_=_id("dttimes-enable", index), class_="time enabled disable") + page.div.close() + + # Put the origin somewhere. + + self.control("recur-origin", "hidden", p.origin or "") + + else: + page.div(class_="dt disabled") + if not read_only: + page.label("Specify end date", for_=_id("dtend-enable", index), class_="enable") + page.div.close() + page.div(class_="dt enabled") + self.date_controls(_name("dtend", "recur", index), p.get_form_end(), index=index, show_tzid=False, read_only=read_only) + if not read_only: + page.br() + page.label("End on same day", for_=_id("dtend-enable", index), class_="disable") + page.div.close() + + page.td.close() + + # Show label as attendee. + + else: + self.show_recurrence_label(p, recurrenceid, recurrenceids, show_start) + + def show_recurrence_label(self, period, recurrenceid, recurrenceids, show_start): + + """ + Show datetime details for the given 'period', employing any + 'recurrenceid' and 'recurrenceids' for the object to configure the + displayed information. + + If 'show_start' is set to a true value, the start details will be shown; + otherwise, the end details will be shown. + """ + + page = self.page + + p = event_period_from_period(period) + replaced = not recurrenceid and p.is_replaced(recurrenceids) + + css = " ".join([ + replaced and "replaced" or "", + p.is_affected(recurrenceid) and "affected" or "" + ]) + + formdate = show_start and p.get_form_start() or p.get_form_end() + dt = formdate.as_datetime() + if dt: + page.td(self.format_datetime(dt, "long"), class_=css) + else: + page.td("(Unrecognised date)") + + def get_date_control_values(self, name, multiple=False, tzid_name=None): + + """ + Return a dictionary containing date, time and tzid entries for fields + starting with 'name'. If 'multiple' is set to a true value, many + dictionaries will be returned corresponding to a collection of + datetimes. If 'tzid_name' is specified, the time zone information will + be acquired from a field starting with 'tzid_name' instead of 'name'. + """ + + args = self.env.get_args() + + dates = args.get("%s-date" % name, []) + hours = args.get("%s-hour" % name, []) + minutes = args.get("%s-minute" % name, []) + seconds = args.get("%s-second" % name, []) + tzids = args.get("%s-tzid" % (tzid_name or name), []) + + # Handle absent values by employing None values. + + field_values = map(None, dates, hours, minutes, seconds, tzids) + + if not field_values and not multiple: + all_values = FormDate() + else: + all_values = [] + for date, hour, minute, second, tzid in field_values: + value = FormDate(date, hour, minute, second, tzid or self.get_tzid()) + + # Return a single value or append to a collection of all values. + + if not multiple: + return value + else: + all_values.append(value) + + return all_values + # vim: tabstop=4 expandtab shiftwidth=4