imip-agent

imipweb/data.py

1053:068aa85f0c45
2016-02-08 Paul Boddie Made the retraction operation a complete transaction. Tidied up the locking and unlocking function application.
     1 #!/usr/bin/env python     2      3 """     4 Web interface data abstractions.     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, timedelta    23 from imiptools.dates import end_date_from_calendar, end_date_to_calendar, \    24                             format_datetime, get_datetime, get_end_of_day, \    25                             to_date    26 from imiptools.period import RecurringPeriod    27     28 class PeriodError(Exception):    29     pass    30     31 class EventPeriod(RecurringPeriod):    32     33     """    34     A simple period plus attribute details, compatible with RecurringPeriod, and    35     intended to represent information obtained from an iCalendar resource.    36     """    37     38     def __init__(self, start, end, tzid=None, origin=None, start_attr=None, end_attr=None, form_start=None, form_end=None, replaced=False):    39     40         """    41         Initialise a period with the given 'start' and 'end' datetimes, together    42         with optional 'start_attr' and 'end_attr' metadata, 'form_start' and    43         'form_end' values provided as textual input, and with an optional    44         'origin' indicating the kind of period this object describes.    45         """    46     47         RecurringPeriod.__init__(self, start, end, tzid, origin, start_attr, end_attr)    48         self.form_start = form_start    49         self.form_end = form_end    50         self.replaced = replaced    51     52     def as_tuple(self):    53         return self.start, self.end, self.tzid, self.origin, self.start_attr, self.end_attr, self.form_start, self.form_end, self.replaced    54     55     def __repr__(self):    56         return "EventPeriod%r" % (self.as_tuple(),)    57     58     def as_event_period(self):    59         return self    60     61     def get_start_item(self):    62         return self.get_start(), self.get_start_attr()    63     64     def get_end_item(self):    65         return self.get_end(), self.get_end_attr()    66     67     # Form data compatibility methods.    68     69     def get_form_start(self):    70         if not self.form_start:    71             self.form_start = self.get_form_date(self.get_start(), self.start_attr)    72         return self.form_start    73     74     def get_form_end(self):    75         if not self.form_end:    76             self.form_end = self.get_form_date(end_date_from_calendar(self.get_end()), self.end_attr)    77         return self.form_end    78     79     def as_form_period(self):    80         return FormPeriod(    81             self.get_form_start(),    82             self.get_form_end(),    83             isinstance(self.end, datetime) or self.get_start() != self.get_end() - timedelta(1),    84             isinstance(self.start, datetime) or isinstance(self.end, datetime),    85             self.tzid,    86             self.origin,    87             self.replaced    88             )    89     90     def get_form_date(self, dt, attr=None):    91         return FormDate(    92             format_datetime(to_date(dt)),    93             isinstance(dt, datetime) and str(dt.hour) or None,    94             isinstance(dt, datetime) and str(dt.minute) or None,    95             isinstance(dt, datetime) and str(dt.second) or None,    96             attr and attr.get("TZID") or None,    97             dt, attr    98             )    99    100 class FormPeriod(RecurringPeriod):   101    102     "A period whose information originates from a form."   103    104     def __init__(self, start, end, end_enabled=True, times_enabled=True, tzid=None, origin=None, replaced=False):   105         self.start = start   106         self.end = end   107         self.end_enabled = end_enabled   108         self.times_enabled = times_enabled   109         self.tzid = tzid   110         self.origin = origin   111         self.replaced = replaced   112    113     def as_tuple(self):   114         return self.start, self.end, self.end_enabled, self.times_enabled, self.tzid, self.origin, self.replaced   115    116     def __repr__(self):   117         return "FormPeriod%r" % (self.as_tuple(),)   118    119     def as_event_period(self, index=None):   120    121         """   122         Return a converted version of this object as an event period suitable   123         for iCalendar usage. If 'index' is indicated, include it in any error   124         raised in the conversion process.   125         """   126    127         dtstart, dtstart_attr = self.get_start_item()   128         if not dtstart:   129             raise PeriodError(*[index is not None and ("dtstart", index) or "dtstart"])   130    131         dtend, dtend_attr = self.get_end_item()   132         if not dtend:   133             raise PeriodError(*[index is not None and ("dtend", index) or "dtend"])   134    135         if dtstart > dtend:   136             raise PeriodError(*[   137                 index is not None and ("dtstart", index) or "dtstart",   138                 index is not None and ("dtend", index) or "dtend"   139                 ])   140    141         return EventPeriod(dtstart, end_date_to_calendar(dtend), self.tzid, self.origin, dtstart_attr, dtend_attr, self.start, self.end, self.replaced)   142    143     # Period data methods.   144    145     def get_start(self):   146         return self.start.as_datetime(self.times_enabled)   147    148     def get_end(self):   149    150         # Handle specified end datetimes.   151    152         if self.end_enabled:   153             dtend = self.end.as_datetime(self.times_enabled)   154             if not dtend:   155                 return None   156    157         # Handle same day times.   158    159         elif self.times_enabled:   160             formdate = FormDate(self.start.date, self.end.hour, self.end.minute, self.end.second, self.end.tzid)   161             dtend = formdate.as_datetime(self.times_enabled)   162             if not dtend:   163                 return None   164    165         # Otherwise, treat the end date as the start date. Datetimes are   166         # handled by making the event occupy the rest of the day.   167    168         else:   169             dtstart, dtstart_attr = self.get_start_item()   170             if dtstart:   171                 if isinstance(dtstart, datetime):   172                     dtend = get_end_of_day(dtstart, dtstart_attr["TZID"])   173                 else:   174                     dtend = dtstart   175             else:   176                 return None   177    178         return dtend   179    180     def get_start_attr(self):   181         return self.start.get_attributes(self.times_enabled)   182    183     def get_end_attr(self):   184         return self.end.get_attributes(self.times_enabled)   185    186     # Form data methods.   187    188     def get_form_start(self):   189         return self.start   190    191     def get_form_end(self):   192         return self.end   193    194     def as_form_period(self):   195         return self   196    197 class FormDate:   198    199     "Date information originating from form information."   200    201     def __init__(self, date=None, hour=None, minute=None, second=None, tzid=None, dt=None, attr=None):   202         self.date = date   203         self.hour = hour   204         self.minute = minute   205         self.second = second   206         self.tzid = tzid   207         self.dt = dt   208         self.attr = attr   209    210     def as_tuple(self):   211         return self.date, self.hour, self.minute, self.second, self.tzid, self.dt, self.attr   212    213     def __repr__(self):   214         return "FormDate%r" % (self.as_tuple(),)   215    216     def get_component(self, value):   217         return (value or "").rjust(2, "0")[:2]   218    219     def get_hour(self):   220         return self.get_component(self.hour)   221    222     def get_minute(self):   223         return self.get_component(self.minute)   224    225     def get_second(self):   226         return self.get_component(self.second)   227    228     def get_date_string(self):   229         return self.date or ""   230    231     def get_datetime_string(self):   232         if not self.date:   233             return ""   234    235         hour = self.hour; minute = self.minute; second = self.second   236    237         if hour or minute or second:   238             time = "T%s%s%s" % tuple(map(self.get_component, (hour, minute, second)))   239         else:   240             time = ""   241                242         return "%s%s" % (self.date, time)   243    244     def get_tzid(self):   245         return self.tzid   246    247     def as_datetime(self, with_time=True):   248    249         "Return a datetime for this object."   250    251         # Return any original datetime details.   252    253         if self.dt:   254             return self.dt   255    256         # Otherwise, construct a datetime.   257    258         s, attr = self.as_datetime_item(with_time)   259         if s:   260             return get_datetime(s, attr)   261         else:   262             return None   263    264     def as_datetime_item(self, with_time=True):   265    266         """   267         Return a (datetime string, attr) tuple for the datetime information   268         provided by this object, where both tuple elements will be None if no   269         suitable date or datetime information exists.   270         """   271    272         s = None   273         if with_time:   274             s = self.get_datetime_string()   275             attr = self.get_attributes(True)   276         if not s:   277             s = self.get_date_string()   278             attr = self.get_attributes(False)   279         if not s:   280             return None, None   281         return s, attr   282    283     def get_attributes(self, with_time=True):   284    285         "Return attributes for the date or datetime represented by this object."   286    287         if with_time:   288             return {"TZID" : self.get_tzid(), "VALUE" : "DATE-TIME"}   289         else:   290             return {"VALUE" : "DATE"}   291    292 def event_period_from_period(period):   293    294     """   295     Convert a 'period' to one suitable for use in an iCalendar representation.   296     In an "event period" representation, the end day of any date-level event is   297     encoded as the "day after" the last day actually involved in the event.   298     """   299    300     if isinstance(period, EventPeriod):   301         return period   302     elif isinstance(period, FormPeriod):   303         return period.as_event_period()   304     else:   305         dtstart, dtstart_attr = period.get_start_item()   306         dtend, dtend_attr = period.get_end_item()   307         if not isinstance(period, RecurringPeriod):   308             dtend = end_date_to_calendar(dtend)   309         return EventPeriod(dtstart, dtend, period.tzid, period.origin, dtstart_attr, dtend_attr)   310    311 def form_period_from_period(period):   312    313     """   314     Convert a 'period' into a representation usable in a user-editable form.   315     In a "form period" representation, the end day of any date-level event is   316     presented in a "natural" form, not the iCalendar "day after" form.   317     """   318    319     if isinstance(period, EventPeriod):   320         return period.as_form_period()   321     elif isinstance(period, FormPeriod):   322         return period   323     else:   324         return event_period_from_period(period).as_form_period()   325    326 # vim: tabstop=4 expandtab shiftwidth=4