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, ClientForObject 24 from imiptools.data import get_uri, uri_values 25 from imiptools.dates import get_recurrence_start_point 26 from imiptools.period import remove_period, remove_affected_period 27 from imipweb.env import CGIEnvironment 28 import babel.dates 29 import imip_store 30 import markup 31 32 class Resource: 33 34 "A Web application resource." 35 36 def __init__(self, resource=None): 37 38 """ 39 Initialise a resource, allowing it to share the environment of any given 40 existing 'resource'. 41 """ 42 43 self.encoding = "utf-8" 44 self.env = CGIEnvironment(self.encoding) 45 46 self.objects = {} 47 self.locale = None 48 self.requests = None 49 50 self.out = resource and resource.out or self.env.get_output() 51 self.page = resource and resource.page or markup.page() 52 self.html_ids = None 53 54 # Presentation methods. 55 56 def new_page(self, title): 57 self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css")) 58 self.html_ids = set() 59 60 def status(self, code, message): 61 self.header("Status", "%s %s" % (code, message)) 62 63 def header(self, header, value): 64 print >>self.out, "%s: %s" % (header, value) 65 66 def no_user(self): 67 self.status(403, "Forbidden") 68 self.new_page(title="Forbidden") 69 self.page.p("You are not logged in and thus cannot access scheduling requests.") 70 71 def no_page(self): 72 self.status(404, "Not Found") 73 self.new_page(title="Not Found") 74 self.page.p("No page is provided at the given address.") 75 76 def redirect(self, url): 77 self.status(302, "Redirect") 78 self.header("Location", url) 79 self.new_page(title="Redirect") 80 self.page.p("Redirecting to: %s" % url) 81 82 def link_to(self, uid, recurrenceid=None, section=None): 83 84 """ 85 Return a link to an object with the given 'uid', 'recurrenceid' and 86 'section'. See get_identifiers for the decoding of such links. 87 """ 88 89 path = [uid] 90 if recurrenceid: 91 path.append(recurrenceid) 92 if section: 93 path.append(section) 94 return self.env.new_url("/".join(path)) 95 96 # Control naming helpers. 97 98 def element_identifier(self, name, index=None): 99 return index is not None and "%s-%d" % (name, index) or name 100 101 def element_name(self, name, suffix, index=None): 102 return index is not None and "%s-%s" % (name, suffix) or name 103 104 def element_enable(self, index=None): 105 return index is not None and str(index) or "enable" 106 107 # Access to objects. 108 109 def get_identifiers(self, path_info): 110 111 """ 112 Return identifiers provided by 'path_info', potentially encoded by 113 'link_to'. 114 """ 115 116 parts = path_info.lstrip("/").split("/") 117 118 # UID only. 119 120 if len(parts) == 1: 121 return parts[0], None, None 122 123 # UID and RECURRENCE-ID or UID and section. 124 125 elif len(parts) == 2: 126 if parts[1] == "counter": 127 return parts[0], None, "counters" 128 else: 129 return parts[0], parts[1], parts[2] == "counter" and "counters" or None 130 131 # UID, RECURRENCE-ID and section. 132 133 else: 134 return parts[:3] 135 136 def _get_object(self, uid, recurrenceid=None, section=None): 137 if self.objects.has_key((uid, recurrenceid, section)): 138 return self.objects[(uid, recurrenceid, section)] 139 140 obj = self.objects[(uid, recurrenceid, section)] = self.get_stored_object(uid, recurrenceid, section) 141 return obj 142 143 def _get_recurrences(self, uid): 144 return self.store.get_recurrences(self.user, uid) 145 146 def _get_active_recurrences(self, uid): 147 return self.store.get_active_recurrences(self.user, uid) 148 149 def _get_requests(self): 150 if self.requests is None: 151 self.requests = self.store.get_requests(self.user) 152 return self.requests 153 154 def _have_request(self, uid, recurrenceid=None, type=None, strict=False): 155 return self.store.have_request(self._get_requests(), uid, recurrenceid, type, strict) 156 157 def _get_request_summary(self): 158 159 "Return a list of periods comprising the request summary." 160 161 summary = [] 162 163 for uid, recurrenceid, request_type in self._get_requests(): 164 obj = self.get_stored_object(uid, recurrenceid) 165 if obj: 166 recurrenceids = self._get_active_recurrences(uid) 167 168 # Obtain only active periods, not those replaced by redefined 169 # recurrences, converting to free/busy periods. 170 171 for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()): 172 summary.append(obj.get_freebusy_period(p)) 173 174 return summary 175 176 # Preference methods. 177 178 def get_user_locale(self): 179 if not self.locale: 180 self.locale = self.get_preferences().get("LANG", "en") 181 return self.locale 182 183 # Prettyprinting of dates and times. 184 185 def format_date(self, dt, format): 186 return self._format_datetime(babel.dates.format_date, dt, format) 187 188 def format_time(self, dt, format): 189 return self._format_datetime(babel.dates.format_time, dt, format) 190 191 def format_datetime(self, dt, format): 192 return self._format_datetime( 193 isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date, 194 dt, format) 195 196 def _format_datetime(self, fn, dt, format): 197 return fn(dt, format=format, locale=self.get_user_locale()) 198 199 # Data management methods. 200 201 def remove_request(self, uid, recurrenceid=None): 202 return self.store.dequeue_request(self.user, uid, recurrenceid) 203 204 def remove_event(self, uid, recurrenceid=None): 205 return self.store.remove_event(self.user, uid, recurrenceid) 206 207 class ResourceClient(Resource, Client): 208 209 "A Web application resource and calendar client." 210 211 def __init__(self, resource=None): 212 Resource.__init__(self, resource) 213 user = self.env.get_user() 214 Client.__init__(self, user and get_uri(user) or None) 215 216 class ResourceClientForObject(Resource, ClientForObject): 217 218 "A Web application resource and calendar client for a specific object." 219 220 def __init__(self, resource=None): 221 Resource.__init__(self, resource) 222 user = self.env.get_user() 223 ClientForObject.__init__(self, None, user and get_uri(user) or None) 224 225 # vim: tabstop=4 expandtab shiftwidth=4