paul@1234 | 1 | #!/usr/bin/env python |
paul@1234 | 2 | |
paul@1234 | 3 | """ |
paul@1234 | 4 | Managing free/busy period collections using database representations. |
paul@1234 | 5 | |
paul@1234 | 6 | Copyright (C) 2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk> |
paul@1234 | 7 | |
paul@1234 | 8 | This program is free software; you can redistribute it and/or modify it under |
paul@1234 | 9 | the terms of the GNU General Public License as published by the Free Software |
paul@1234 | 10 | Foundation; either version 3 of the License, or (at your option) any later |
paul@1234 | 11 | version. |
paul@1234 | 12 | |
paul@1234 | 13 | This program is distributed in the hope that it will be useful, but WITHOUT |
paul@1234 | 14 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
paul@1234 | 15 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
paul@1234 | 16 | details. |
paul@1234 | 17 | |
paul@1234 | 18 | You should have received a copy of the GNU General Public License along with |
paul@1234 | 19 | this program. If not, see <http://www.gnu.org/licenses/>. |
paul@1234 | 20 | """ |
paul@1234 | 21 | |
paul@1234 | 22 | from imiptools.dates import format_datetime |
paul@1234 | 23 | from imiptools.freebusy.common import FreeBusyCollectionBase, SupportAttendee, \ |
paul@1234 | 24 | SupportExpires, from_string, to_string |
paul@1234 | 25 | from imiptools.sql import DatabaseOperations |
paul@1234 | 26 | |
paul@1234 | 27 | try: |
paul@1234 | 28 | from cStringIO import StringIO |
paul@1234 | 29 | except ImportError: |
paul@1234 | 30 | from StringIO import StringIO |
paul@1234 | 31 | |
paul@1234 | 32 | # Conversion functions. |
paul@1234 | 33 | |
paul@1234 | 34 | def to_copy_string(s, encoding): |
paul@1234 | 35 | |
paul@1234 | 36 | """ |
paul@1234 | 37 | Encode 's' using 'encoding' as a string suitable for use in tabular data |
paul@1234 | 38 | acceptable to the PostgreSQL COPY command with \N as null. |
paul@1234 | 39 | """ |
paul@1234 | 40 | |
paul@1234 | 41 | s = to_string(s, encoding) |
paul@1234 | 42 | return s is None and "\\N" or s |
paul@1234 | 43 | |
paul@1234 | 44 | def to_copy_file(records): |
paul@1234 | 45 | |
paul@1234 | 46 | """ |
paul@1234 | 47 | Encode the given 'records' and store them in a file-like object for use with |
paul@1234 | 48 | a tabular import mechanism. Return the file-like object. |
paul@1234 | 49 | """ |
paul@1234 | 50 | |
paul@1234 | 51 | io = StringIO() |
paul@1234 | 52 | for values in records: |
paul@1234 | 53 | l = [] |
paul@1234 | 54 | for v in values: |
paul@1234 | 55 | l.append(to_copy_string(v, "utf-8")) |
paul@1234 | 56 | io.write("\t".join(l)) |
paul@1234 | 57 | io.write("\n") |
paul@1234 | 58 | io.seek(0) |
paul@1234 | 59 | return io |
paul@1234 | 60 | |
paul@1234 | 61 | def quote_column(column): |
paul@1234 | 62 | |
paul@1234 | 63 | "Quote 'column' using the SQL keyword quoting notation." |
paul@1234 | 64 | |
paul@1234 | 65 | return '"%s"' % column |
paul@1234 | 66 | |
paul@1234 | 67 | |
paul@1234 | 68 | |
paul@1234 | 69 | # Collection abstractions. |
paul@1234 | 70 | |
paul@1234 | 71 | class FreeBusyDatabaseCollection(FreeBusyCollectionBase, DatabaseOperations): |
paul@1234 | 72 | |
paul@1234 | 73 | """ |
paul@1234 | 74 | An abstraction for a collection of free/busy periods stored in a database |
paul@1234 | 75 | system. |
paul@1234 | 76 | """ |
paul@1234 | 77 | |
paul@1234 | 78 | def __init__(self, cursor, table_name, column_names=None, filter_values=None, |
paul@1234 | 79 | mutable=True, paramstyle=None): |
paul@1234 | 80 | |
paul@1234 | 81 | """ |
paul@1234 | 82 | Initialise the collection with the given 'cursor' and with the |
paul@1234 | 83 | 'table_name', 'column_names' and 'filter_values' configuring the |
paul@1234 | 84 | selection of data. If 'mutable' is indicated, the collection may be |
paul@1234 | 85 | changed; otherwise, an exception will be raised. |
paul@1234 | 86 | """ |
paul@1234 | 87 | |
paul@1234 | 88 | FreeBusyCollectionBase.__init__(self, mutable) |
paul@1234 | 89 | DatabaseOperations.__init__(self, column_names, filter_values, paramstyle) |
paul@1234 | 90 | self.cursor = cursor |
paul@1234 | 91 | self.table_name = table_name |
paul@1234 | 92 | |
paul@1236 | 93 | self.period_columns = self.period_class.period_columns |
paul@1236 | 94 | |
paul@1234 | 95 | # List emulation methods. |
paul@1234 | 96 | |
paul@1234 | 97 | def __nonzero__(self): |
paul@1234 | 98 | return len(self) and True or False |
paul@1234 | 99 | |
paul@1234 | 100 | def __iter__(self): |
paul@1234 | 101 | query, values = self.get_query( |
paul@1234 | 102 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 103 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 104 | "table" : self.table_name |
paul@1234 | 105 | }) |
paul@1234 | 106 | self.cursor.execute(query, values) |
paul@1234 | 107 | return iter(map(lambda t: self.make_period(t), self.cursor.fetchall())) |
paul@1234 | 108 | |
paul@1234 | 109 | def __len__(self): |
paul@1234 | 110 | query, values = self.get_query( |
paul@1234 | 111 | "select count(*) from %(table)s :condition" % { |
paul@1234 | 112 | "table" : self.table_name |
paul@1234 | 113 | }) |
paul@1234 | 114 | self.cursor.execute(query, values) |
paul@1234 | 115 | result = self.cursor.fetchone() |
paul@1234 | 116 | return result and int(result[0]) or 0 |
paul@1234 | 117 | |
paul@1234 | 118 | def __getitem__(self, i): |
paul@1234 | 119 | return list(iter(self))[i] |
paul@1234 | 120 | |
paul@1236 | 121 | # Dictionary emulation methods (even though this is not a mapping). |
paul@1236 | 122 | |
paul@1236 | 123 | def clear(self): |
paul@1236 | 124 | query, values = self.get_query( |
paul@1236 | 125 | "delete from %(table)s :condition" % { |
paul@1236 | 126 | "table" : self.table_name |
paul@1236 | 127 | }) |
paul@1236 | 128 | self.cursor.execute(query, values) |
paul@1236 | 129 | |
paul@1234 | 130 | # Operations. |
paul@1234 | 131 | |
paul@1234 | 132 | def insert_period(self, period): |
paul@1234 | 133 | |
paul@1234 | 134 | "Insert the given 'period' into the collection." |
paul@1234 | 135 | |
paul@1234 | 136 | self._check_mutable() |
paul@1234 | 137 | |
paul@1234 | 138 | columns, values = self.period_columns, period.as_tuple(string_datetimes=True) |
paul@1234 | 139 | |
paul@1234 | 140 | query, values = self.get_query( |
paul@1234 | 141 | "insert into %(table)s (:columns) values (:values)" % { |
paul@1234 | 142 | "table" : self.table_name |
paul@1234 | 143 | }, |
paul@1234 | 144 | columns, [to_string(v, "utf-8") for v in values]) |
paul@1234 | 145 | |
paul@1234 | 146 | self.cursor.execute(query, values) |
paul@1234 | 147 | |
paul@1234 | 148 | def insert_periods(self, periods): |
paul@1234 | 149 | |
paul@1234 | 150 | "Insert the given 'periods' into the collection." |
paul@1234 | 151 | |
paul@1234 | 152 | if not hasattr(self.cursor, "copy_from"): |
paul@1234 | 153 | return FreeBusyCollectionBase.insert_periods(self, periods) |
paul@1234 | 154 | |
paul@1234 | 155 | self._check_mutable() |
paul@1234 | 156 | |
paul@1234 | 157 | columns = self.merge_default_columns(self.period_columns) |
paul@1234 | 158 | |
paul@1234 | 159 | all_values = [] |
paul@1234 | 160 | for period in periods: |
paul@1234 | 161 | all_values.append(self.merge_default_values(period.as_tuple(string_datetimes=True))) |
paul@1234 | 162 | |
paul@1234 | 163 | f = to_copy_file(all_values) |
paul@1234 | 164 | |
paul@1234 | 165 | # Copy from the file-like object to the table. |
paul@1234 | 166 | |
paul@1234 | 167 | self.cursor.copy_from(f, self.table_name, columns=map(quote_column, columns)) |
paul@1234 | 168 | |
paul@1234 | 169 | def remove_periods(self, periods): |
paul@1234 | 170 | |
paul@1234 | 171 | "Remove the given 'periods' from the collection." |
paul@1234 | 172 | |
paul@1234 | 173 | self._check_mutable() |
paul@1234 | 174 | |
paul@1234 | 175 | for period in periods: |
paul@1234 | 176 | values = period.as_tuple(string_datetimes=True) |
paul@1234 | 177 | |
paul@1234 | 178 | query, values = self.get_query( |
paul@1234 | 179 | "delete from %(table)s :condition" % { |
paul@1234 | 180 | "table" : self.table_name |
paul@1234 | 181 | }, |
paul@1234 | 182 | self.period_columns, [to_string(v, "utf-8") for v in values]) |
paul@1234 | 183 | |
paul@1234 | 184 | self.cursor.execute(query, values) |
paul@1234 | 185 | |
paul@1243 | 186 | def remove_periods_before(self, period): |
paul@1243 | 187 | |
paul@1243 | 188 | "Remove the entries in the collection before 'period'." |
paul@1243 | 189 | |
paul@1243 | 190 | end = format_datetime(period.get_start_point()) |
paul@1243 | 191 | |
paul@1243 | 192 | columns, values = [], [] |
paul@1243 | 193 | |
paul@1243 | 194 | if end: |
paul@1243 | 195 | columns.append("end <= ?") |
paul@1243 | 196 | values.append(end) |
paul@1243 | 197 | |
paul@1243 | 198 | query, values = self.get_query( |
paul@1243 | 199 | "delete from %(table)s :condition" % { |
paul@1243 | 200 | "columns" : self.columnlist(self.period_columns), |
paul@1243 | 201 | "table" : self.table_name |
paul@1243 | 202 | }, |
paul@1243 | 203 | columns, values) |
paul@1243 | 204 | |
paul@1243 | 205 | self.cursor.execute(query, values) |
paul@1243 | 206 | |
paul@1234 | 207 | def remove_event_periods(self, uid, recurrenceid=None, participant=None): |
paul@1234 | 208 | |
paul@1234 | 209 | """ |
paul@1234 | 210 | Remove from the collection all periods associated with 'uid' and |
paul@1234 | 211 | 'recurrenceid' (which if omitted causes the "parent" object's periods to |
paul@1234 | 212 | be referenced). |
paul@1234 | 213 | |
paul@1234 | 214 | If 'participant' is specified, only remove periods for which the |
paul@1234 | 215 | participant is given as attending. |
paul@1234 | 216 | |
paul@1234 | 217 | Return the removed periods. |
paul@1234 | 218 | """ |
paul@1234 | 219 | |
paul@1234 | 220 | self._check_mutable() |
paul@1234 | 221 | |
paul@1234 | 222 | columns, values = ["object_uid"], [uid] |
paul@1234 | 223 | |
paul@1234 | 224 | if recurrenceid: |
paul@1234 | 225 | columns.append("object_recurrenceid") |
paul@1234 | 226 | values.append(recurrenceid) |
paul@1234 | 227 | else: |
paul@1234 | 228 | columns.append("object_recurrenceid is null") |
paul@1234 | 229 | |
paul@1234 | 230 | if participant: |
paul@1234 | 231 | columns.append("attendee") |
paul@1234 | 232 | values.append(participant) |
paul@1234 | 233 | |
paul@1234 | 234 | query, _values = self.get_query( |
paul@1234 | 235 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 236 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 237 | "table" : self.table_name |
paul@1234 | 238 | }, |
paul@1234 | 239 | columns, values) |
paul@1234 | 240 | |
paul@1234 | 241 | self.cursor.execute(query, _values) |
paul@1234 | 242 | removed = self.cursor.fetchall() |
paul@1234 | 243 | |
paul@1234 | 244 | query, values = self.get_query( |
paul@1234 | 245 | "delete from %(table)s :condition" % { |
paul@1234 | 246 | "table" : self.table_name |
paul@1234 | 247 | }, |
paul@1234 | 248 | columns, values) |
paul@1234 | 249 | |
paul@1234 | 250 | self.cursor.execute(query, values) |
paul@1234 | 251 | |
paul@1234 | 252 | return map(lambda t: self.make_period(t), removed) |
paul@1234 | 253 | |
paul@1234 | 254 | # Specific period removal when updating event details. |
paul@1234 | 255 | |
paul@1234 | 256 | remove_specific_event_periods = remove_event_periods |
paul@1234 | 257 | |
paul@1234 | 258 | def remove_additional_periods(self, uid, recurrenceids=None): |
paul@1234 | 259 | |
paul@1234 | 260 | """ |
paul@1234 | 261 | Remove from the collection all periods associated with 'uid' having a |
paul@1234 | 262 | recurrence identifier indicating an additional or modified period. |
paul@1234 | 263 | |
paul@1234 | 264 | If 'recurrenceids' is specified, remove all periods associated with |
paul@1234 | 265 | 'uid' that do not have a recurrence identifier in the given list. |
paul@1234 | 266 | |
paul@1234 | 267 | Return the removed periods. |
paul@1234 | 268 | """ |
paul@1234 | 269 | |
paul@1234 | 270 | self._check_mutable() |
paul@1234 | 271 | |
paul@1234 | 272 | if not recurrenceids: |
paul@1234 | 273 | columns, values = ["object_uid", "object_recurrenceid is not null"], [uid] |
paul@1234 | 274 | else: |
paul@1234 | 275 | columns, values = ["object_uid", "object_recurrenceid not in ?", "object_recurrenceid is not null"], [uid, tuple(recurrenceids)] |
paul@1234 | 276 | |
paul@1234 | 277 | query, _values = self.get_query( |
paul@1234 | 278 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 279 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 280 | "table" : self.table_name |
paul@1234 | 281 | }, |
paul@1234 | 282 | columns, values) |
paul@1234 | 283 | |
paul@1234 | 284 | self.cursor.execute(query, _values) |
paul@1234 | 285 | removed = self.cursor.fetchall() |
paul@1234 | 286 | |
paul@1234 | 287 | query, values = self.get_query( |
paul@1234 | 288 | "delete from %(table)s :condition" % { |
paul@1234 | 289 | "table" : self.table_name |
paul@1234 | 290 | }, |
paul@1234 | 291 | columns, values) |
paul@1234 | 292 | |
paul@1234 | 293 | self.cursor.execute(query, values) |
paul@1234 | 294 | |
paul@1234 | 295 | return map(lambda t: self.make_period(t), removed) |
paul@1234 | 296 | |
paul@1234 | 297 | def remove_affected_period(self, uid, start, participant=None): |
paul@1234 | 298 | |
paul@1234 | 299 | """ |
paul@1234 | 300 | Remove from the collection the period associated with 'uid' that |
paul@1234 | 301 | provides an occurrence starting at the given 'start' (provided by a |
paul@1234 | 302 | recurrence identifier, converted to a datetime). A recurrence identifier |
paul@1234 | 303 | is used to provide an alternative time period whilst also acting as a |
paul@1234 | 304 | reference to the originally-defined occurrence. |
paul@1234 | 305 | |
paul@1234 | 306 | If 'participant' is specified, only remove periods for which the |
paul@1234 | 307 | participant is given as attending. |
paul@1234 | 308 | |
paul@1234 | 309 | Return any removed period in a list. |
paul@1234 | 310 | """ |
paul@1234 | 311 | |
paul@1234 | 312 | self._check_mutable() |
paul@1234 | 313 | |
paul@1234 | 314 | start = format_datetime(start) |
paul@1234 | 315 | |
paul@1234 | 316 | columns, values = ["object_uid", "start", "object_recurrenceid is null"], [uid, start] |
paul@1234 | 317 | |
paul@1234 | 318 | if participant: |
paul@1234 | 319 | columns.append("attendee") |
paul@1234 | 320 | values.append(participant) |
paul@1234 | 321 | |
paul@1234 | 322 | query, _values = self.get_query( |
paul@1234 | 323 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 324 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 325 | "table" : self.table_name |
paul@1234 | 326 | }, |
paul@1234 | 327 | columns, values) |
paul@1234 | 328 | |
paul@1234 | 329 | self.cursor.execute(query, _values) |
paul@1234 | 330 | removed = self.cursor.fetchall() |
paul@1234 | 331 | |
paul@1234 | 332 | query, values = self.get_query( |
paul@1234 | 333 | "delete from %(table)s :condition" % { |
paul@1234 | 334 | "table" : self.table_name |
paul@1234 | 335 | }, |
paul@1234 | 336 | columns, values) |
paul@1234 | 337 | |
paul@1234 | 338 | self.cursor.execute(query, values) |
paul@1234 | 339 | |
paul@1234 | 340 | return map(lambda t: self.make_period(t), removed) |
paul@1234 | 341 | |
paul@1234 | 342 | def periods_from(self, period): |
paul@1234 | 343 | |
paul@1234 | 344 | "Return the entries in the collection at or after 'period'." |
paul@1234 | 345 | |
paul@1234 | 346 | start = format_datetime(period.get_start_point()) |
paul@1234 | 347 | |
paul@1234 | 348 | columns, values = [], [] |
paul@1234 | 349 | |
paul@1234 | 350 | if start: |
paul@1234 | 351 | columns.append("start >= ?") |
paul@1234 | 352 | values.append(start) |
paul@1234 | 353 | |
paul@1234 | 354 | query, values = self.get_query( |
paul@1234 | 355 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 356 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 357 | "table" : self.table_name |
paul@1234 | 358 | }, |
paul@1234 | 359 | columns, values) |
paul@1234 | 360 | |
paul@1234 | 361 | self.cursor.execute(query, values) |
paul@1234 | 362 | |
paul@1234 | 363 | return map(lambda t: self.make_period(t), self.cursor.fetchall()) |
paul@1234 | 364 | |
paul@1234 | 365 | def periods_until(self, period): |
paul@1234 | 366 | |
paul@1234 | 367 | "Return the entries in the collection before 'period'." |
paul@1234 | 368 | |
paul@1234 | 369 | end = format_datetime(period.get_end_point()) |
paul@1234 | 370 | |
paul@1234 | 371 | columns, values = [], [] |
paul@1234 | 372 | |
paul@1234 | 373 | if end: |
paul@1234 | 374 | columns.append("start < ?") |
paul@1234 | 375 | values.append(end) |
paul@1234 | 376 | |
paul@1234 | 377 | query, values = self.get_query( |
paul@1234 | 378 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 379 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 380 | "table" : self.table_name |
paul@1234 | 381 | }, |
paul@1234 | 382 | columns, values) |
paul@1234 | 383 | |
paul@1234 | 384 | self.cursor.execute(query, values) |
paul@1234 | 385 | |
paul@1234 | 386 | return map(lambda t: self.make_period(t), self.cursor.fetchall()) |
paul@1234 | 387 | |
paul@1234 | 388 | def get_overlapping(self, periods): |
paul@1234 | 389 | |
paul@1234 | 390 | """ |
paul@1234 | 391 | Return the entries in the collection providing periods overlapping with |
paul@1234 | 392 | the given sorted collection of 'periods'. |
paul@1234 | 393 | """ |
paul@1234 | 394 | |
paul@1234 | 395 | overlapping = set() |
paul@1234 | 396 | |
paul@1234 | 397 | for period in periods: |
paul@1234 | 398 | columns, values = self._get_period_values(period) |
paul@1234 | 399 | |
paul@1234 | 400 | query, values = self.get_query( |
paul@1234 | 401 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 402 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 403 | "table" : self.table_name |
paul@1234 | 404 | }, |
paul@1234 | 405 | columns, values) |
paul@1234 | 406 | |
paul@1234 | 407 | self.cursor.execute(query, values) |
paul@1234 | 408 | |
paul@1234 | 409 | overlapping.update(map(lambda t: self.make_period(t), self.cursor.fetchall())) |
paul@1234 | 410 | |
paul@1234 | 411 | overlapping = list(overlapping) |
paul@1234 | 412 | overlapping.sort() |
paul@1234 | 413 | return overlapping |
paul@1234 | 414 | |
paul@1234 | 415 | def remove_overlapping(self, period): |
paul@1234 | 416 | |
paul@1234 | 417 | "Remove all periods overlapping with 'period' from the collection." |
paul@1234 | 418 | |
paul@1234 | 419 | self._check_mutable() |
paul@1234 | 420 | |
paul@1234 | 421 | columns, values = self._get_period_values(period) |
paul@1234 | 422 | |
paul@1234 | 423 | query, values = self.get_query( |
paul@1234 | 424 | "delete from %(table)s :condition" % { |
paul@1234 | 425 | "table" : self.table_name |
paul@1234 | 426 | }, |
paul@1234 | 427 | columns, values) |
paul@1234 | 428 | |
paul@1234 | 429 | self.cursor.execute(query, values) |
paul@1234 | 430 | |
paul@1234 | 431 | def _get_period_values(self, period): |
paul@1234 | 432 | |
paul@1234 | 433 | start = format_datetime(period.get_start_point()) |
paul@1234 | 434 | end = format_datetime(period.get_end_point()) |
paul@1234 | 435 | |
paul@1234 | 436 | columns, values = [], [] |
paul@1234 | 437 | |
paul@1234 | 438 | if end: |
paul@1234 | 439 | columns.append("start < ?") |
paul@1234 | 440 | values.append(end) |
paul@1234 | 441 | if start: |
paul@1234 | 442 | columns.append("end > ?") |
paul@1234 | 443 | values.append(start) |
paul@1234 | 444 | |
paul@1234 | 445 | return columns, values |
paul@1234 | 446 | |
paul@1234 | 447 | class FreeBusyGroupDatabaseCollection(SupportAttendee, FreeBusyDatabaseCollection): |
paul@1234 | 448 | |
paul@1234 | 449 | "A collection of quota group free/busy objects." |
paul@1234 | 450 | |
paul@1234 | 451 | def remove_specific_event_periods(self, uid, recurrenceid=None, attendee=None): |
paul@1234 | 452 | |
paul@1234 | 453 | """ |
paul@1234 | 454 | Remove from the collection all periods associated with 'uid' and |
paul@1234 | 455 | 'recurrenceid' (which if omitted causes the "parent" object's periods to |
paul@1234 | 456 | be referenced) and any 'attendee'. |
paul@1234 | 457 | |
paul@1234 | 458 | Return the removed periods. |
paul@1234 | 459 | """ |
paul@1234 | 460 | |
paul@1234 | 461 | self._check_mutable() |
paul@1234 | 462 | |
paul@1234 | 463 | columns, values = ["object_uid"], [uid] |
paul@1234 | 464 | |
paul@1234 | 465 | if recurrenceid: |
paul@1234 | 466 | columns.append("object_recurrenceid") |
paul@1234 | 467 | values.append(recurrenceid) |
paul@1234 | 468 | else: |
paul@1234 | 469 | columns.append("object_recurrenceid is null") |
paul@1234 | 470 | |
paul@1234 | 471 | if attendee: |
paul@1234 | 472 | columns.append("attendee") |
paul@1234 | 473 | values.append(attendee) |
paul@1234 | 474 | else: |
paul@1234 | 475 | columns.append("attendee is null") |
paul@1234 | 476 | |
paul@1234 | 477 | query, _values = self.get_query( |
paul@1234 | 478 | "select %(columns)s from %(table)s :condition" % { |
paul@1234 | 479 | "columns" : self.columnlist(self.period_columns), |
paul@1234 | 480 | "table" : self.table_name |
paul@1234 | 481 | }, |
paul@1234 | 482 | columns, values) |
paul@1234 | 483 | |
paul@1234 | 484 | self.cursor.execute(query, _values) |
paul@1234 | 485 | removed = self.cursor.fetchall() |
paul@1234 | 486 | |
paul@1234 | 487 | query, values = self.get_query( |
paul@1234 | 488 | "delete from %(table)s :condition" % { |
paul@1234 | 489 | "table" : self.table_name |
paul@1234 | 490 | }, |
paul@1234 | 491 | columns, values) |
paul@1234 | 492 | |
paul@1234 | 493 | self.cursor.execute(query, values) |
paul@1234 | 494 | |
paul@1234 | 495 | return map(lambda t: self.make_period(t), removed) |
paul@1234 | 496 | |
paul@1234 | 497 | class FreeBusyOffersDatabaseCollection(SupportExpires, FreeBusyDatabaseCollection): |
paul@1234 | 498 | |
paul@1234 | 499 | "A collection of offered free/busy objects." |
paul@1234 | 500 | |
paul@1234 | 501 | pass |
paul@1234 | 502 | |
paul@1234 | 503 | # vim: tabstop=4 expandtab shiftwidth=4 |