imip-agent

imipweb/resource.py

630:6aca8c28fadc
2015-08-01 Paul Boddie Moved period value list retrieval and active period computation to the object abstraction. Introduced a simpler form of get_periods which returns only explicitly-specified periods or those within an explicitly-terminated window or sequence. Fixed string representations of the different period classes.
     1 #!/usr/bin/env python     2      3 """     4 Common resource functionality for Web calendar clients.     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 datetime import datetime    23 from imiptools.client import Client    24 from imiptools.data import get_uri, uri_values    25 from imiptools.dates import get_recurrence_start_point    26 from imiptools.period import FreeBusyPeriod, is_affected, is_replaced, \    27                              remove_period, remove_affected_period    28 from imipweb.env import CGIEnvironment    29 import babel.dates    30 import imip_store    31 import markup    32     33 class Resource(Client):    34     35     "A Web application resource and calendar client."    36     37     def __init__(self, resource=None):    38         self.encoding = "utf-8"    39         self.env = CGIEnvironment(self.encoding)    40     41         user = self.env.get_user()    42         Client.__init__(self, user and get_uri(user) or None)    43     44         self.locale = None    45         self.requests = None    46     47         self.out = resource and resource.out or self.env.get_output()    48         self.page = resource and resource.page or markup.page()    49         self.html_ids = None    50     51         self.store = imip_store.FileStore()    52         self.objects = {}    53     54         try:    55             self.publisher = imip_store.FilePublisher()    56         except OSError:    57             self.publisher = None    58     59     # Presentation methods.    60     61     def new_page(self, title):    62         self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css"))    63         self.html_ids = set()    64     65     def status(self, code, message):    66         self.header("Status", "%s %s" % (code, message))    67     68     def header(self, header, value):    69         print >>self.out, "%s: %s" % (header, value)    70     71     def no_user(self):    72         self.status(403, "Forbidden")    73         self.new_page(title="Forbidden")    74         self.page.p("You are not logged in and thus cannot access scheduling requests.")    75     76     def no_page(self):    77         self.status(404, "Not Found")    78         self.new_page(title="Not Found")    79         self.page.p("No page is provided at the given address.")    80     81     def redirect(self, url):    82         self.status(302, "Redirect")    83         self.header("Location", url)    84         self.new_page(title="Redirect")    85         self.page.p("Redirecting to: %s" % url)    86     87     def link_to(self, uid, recurrenceid=None):    88         if recurrenceid:    89             return self.env.new_url("/".join([uid, recurrenceid]))    90         else:    91             return self.env.new_url(uid)    92     93     # Control naming helpers.    94     95     def element_identifier(self, name, index=None):    96         return index is not None and "%s-%d" % (name, index) or name    97     98     def element_name(self, name, suffix, index=None):    99         return index is not None and "%s-%s" % (name, suffix) or name   100    101     def element_enable(self, index=None):   102         return index is not None and str(index) or "enable"   103    104     # Access to objects.   105    106     def _get_identifiers(self, path_info):   107         parts = path_info.lstrip("/").split("/")   108         if len(parts) == 1:   109             return parts[0], None   110         else:   111             return parts[:2]   112    113     def _get_object(self, uid, recurrenceid=None):   114         if self.objects.has_key((uid, recurrenceid)):   115             return self.objects[(uid, recurrenceid)]   116    117         obj = self.objects[(uid, recurrenceid)] = self.get_stored_object(uid, recurrenceid)   118         return obj   119    120     def _get_recurrences(self, uid):   121         return self.store.get_recurrences(self.user, uid)   122    123     def _get_requests(self):   124         if self.requests is None:   125             cancellations = self.store.get_cancellations(self.user)   126             requests = set(self.store.get_requests(self.user))   127             self.requests = requests.difference(cancellations)   128         return self.requests   129    130     def _get_request_summary(self):   131    132         "Return a list of periods comprising the request summary."   133    134         summary = []   135    136         for uid, recurrenceid in self._get_requests():   137             obj = self.get_stored_object(uid, recurrenceid)   138             if obj:   139                 recurrenceids = self._get_recurrences(uid)   140    141                 # Obtain only active periods, not those replaced by redefined   142                 # recurrences.   143    144                 for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()):   145    146                     # Convert the periods to more substantial free/busy items.   147    148                     summary.append(   149                         FreeBusyPeriod(   150                             p.get_start(),   151                             p.get_end(),   152                             uid,   153                             obj.get_value("TRANSP"),   154                             recurrenceid,   155                             obj.get_value("SUMMARY"),   156                             obj.get_value("ORGANIZER"),   157                             p.get_tzid()   158                             ))   159         return summary   160    161     # Period and recurrence testing.   162    163     def is_replaced(self, period, recurrenceids):   164         return is_replaced(period, recurrenceids, self.get_tzid())   165    166     def is_affected(self, period, recurrenceid):   167         return is_affected(period, recurrenceid, self.get_tzid())   168    169     # Preference methods.   170    171     def get_user_locale(self):   172         if not self.locale:   173             self.locale = self.get_preferences().get("LANG", "en")   174         return self.locale   175    176     # Prettyprinting of dates and times.   177    178     def format_date(self, dt, format):   179         return self._format_datetime(babel.dates.format_date, dt, format)   180    181     def format_time(self, dt, format):   182         return self._format_datetime(babel.dates.format_time, dt, format)   183    184     def format_datetime(self, dt, format):   185         return self._format_datetime(   186             isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date,   187             dt, format)   188    189     def _format_datetime(self, fn, dt, format):   190         return fn(dt, format=format, locale=self.get_user_locale())   191    192     # Data management methods.   193    194     def remove_request(self, uid, recurrenceid=None):   195         return self.store.dequeue_request(self.user, uid, recurrenceid)   196    197     def remove_event(self, uid, recurrenceid=None):   198         return self.store.remove_event(self.user, uid, recurrenceid)   199    200     def update_freebusy(self, uid, recurrenceid, obj):   201    202         """   203         Update stored free/busy details for the event with the given 'uid' and   204         'recurrenceid' having a representation of 'obj'.   205         """   206    207         is_only_organiser = self.user not in uri_values(obj.get_values("ATTENDEE"))   208    209         freebusy = self.store.get_freebusy(self.user)   210    211         Client.update_freebusy(self, freebusy, self.get_periods(obj),   212             is_only_organiser and "ORG" or obj.get_value("TRANSP"),   213             uid, recurrenceid,   214             obj.get_value("SUMMARY"),   215             obj.get_value("ORGANIZER"))   216    217         # Subtract any recurrences from the free/busy details of a parent   218         # object.   219    220         for recurrenceid in self._get_recurrences(uid):   221             remove_affected_period(freebusy, uid, obj.get_recurrence_start_point(recurrenceid, self.get_tzid()))   222    223         self.store.set_freebusy(self.user, freebusy)   224         self.publish_freebusy(freebusy)   225    226     def remove_from_freebusy(self, uid, recurrenceid=None):   227         freebusy = self.store.get_freebusy(self.user)   228         remove_period(freebusy, uid, recurrenceid)   229         self.store.set_freebusy(self.user, freebusy)   230         self.publish_freebusy(freebusy)   231    232     def publish_freebusy(self, freebusy):   233    234         "Publish the details if configured to share them."   235    236         if self.publisher and self.is_sharing():   237             self.publisher.set_freebusy(self.user, freebusy)   238    239 # vim: tabstop=4 expandtab shiftwidth=4