[FIX]:invalid timezone arg problem
[odoo/odoo.git] / addons / caldav / calendar.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields, osv
24 from time import strftime
25 import time
26 import base64
27 import vobject
28 from dateutil.rrule import *
29 from dateutil import parser
30 from datetime import datetime
31 from time import strftime
32 from pytz import timezone
33
34 # O-1  Optional and can come only once
35 # O-n  Optional and can come more than once
36 # R-1  Required and can come only once
37 # R-n  Required and can come more than once
38
39 class CalDAV(object):
40     __attribute__= {
41     }
42
43     def ical_items(self):
44         return self.__attribute__.items()
45
46     def ical_set(self, name, value, type):
47         if name in self.__attribute__ and self.__attribute__[name]:
48            self.__attribute__[name][type] = value
49         return True
50
51     def ical_get(self, name, type):
52         if self.__attribute__.get(name):
53             val = self.__attribute__.get(name).get(type, None)
54             valtype =  self.__attribute__.get(name).get('type', None)
55             if type == 'value':
56                 if valtype and valtype=='datetime' and val:
57                      val = val.strftime('%Y-%m-%d %H:%M:%S')
58                 if valtype and valtype=='integer' and val:
59                      val = int(val)
60             return  val
61         else:
62              return  self.__attribute__.get(name, None)
63
64     def export_ical(self):
65         pass
66
67     def import_ical(self, cr, uid, ical_data):
68         parsedCal = vobject.readOne(ical_data)
69         for child in parsedCal.getChildren():
70             for cal_data in child.getChildren():
71                 if cal_data.name.lower() == 'attendee':
72                     # This is possible only if Attendee does not inherit osv_memory
73                     attendee = Attendee()
74                     attendee.import_ical(cr, uid, cal_data)
75                 if cal_data.name.lower() in self.__attribute__:
76                     self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
77         return True
78
79 class Calendar(CalDAV, osv.osv_memory):
80     _name = 'caldav.calendar'
81     __attribute__ = {
82         'prodid' : None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
83         'version' : None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
84                            #             or the minimum and maximum range of the iCalendar specification
85                            #             that is required in order to interpret the iCalendar object.
86         'calscale' : None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
87         'method' : None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
88         'vevent'  : None, # Use: O-n, Type: Collection of Event class
89         'vtodo'   : None, # Use: O-n, Type: Collection of ToDo class
90         'vjournal': None, # Use: O-n, Type: Collection of Journal class
91         'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
92         'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
93
94     }
95
96     def import_ical(self, cr, uid, ical_data):
97         # Write openobject data from ical_data
98         ical = vobject.readOne(ical_data)
99         for child in ical.getChildren():
100             child_name = child.name.lower()
101             if child_name == 'vevent':
102                 for event in child.getChildren():
103                     if event.name.lower() =="attendee":
104                         attendee = Attendee()
105                         attendee.import_ical(cr, uid, event)
106                     if event.name.lower() =="valarm":
107                         alarm = Alarm()
108                         alarm.import_ical(cr, uid, event)
109             elif child_name == "vtimezone":
110                 timezone = Timezone()
111                 timezone.import_ical(cr, uid, child)
112         return True
113
114     def export_ical(self, cr, uid, ids):
115         # Read openobject data in ical format
116         ical = vobject.iCalendar()
117         datas = self.browse(cr, uid, ids)
118
119         vcal = ical.add('vcalendar')
120         for name, value in self.ical_items():
121             if not value:
122                 continue
123             if name == 'vevent':
124                 for event in value:
125                     vevent = event.ical_read()
126                     vcal.add(vevent)
127             elif name == 'vtodo':
128                 for todo in value:
129                     vtodo = todo.ical_read()
130                     vcal.add(vtodo)
131             elif name == 'vjournal':
132                 for journal in value:
133                     vjournal = journal.ical_read()
134                     vcal.add(vjournal)
135             elif name == 'vfreebusy':
136                 for freebusy in value:
137                     vfreebusy = freebusy.ical_read()
138                     vcal.add(vfreebusy)
139             elif name == 'vtimezone':
140                 for timezone in value:
141                     vtimezone = timezone.ical_read()
142                     vcal.add(vtimezone)
143             else:
144                 vcal.add(name).value = value
145         s = ical.serialize()
146         return s
147
148     def ical_write(self, data):
149         ical = vobject.readOne(data)
150         for child in ical.getChildren():
151             child_name = child.name.lower()
152             child_value = child.value
153             if child_name == 'vevent':
154                 vevents = []
155                 for event in child.getChildren():
156                     vevent = Event()
157                     vevent.ical_write(event.serialize())
158                     vevents.append(vevent)
159                 self.ical_set(child_name, vevents)
160             else:
161                 self.ical_set(child_name, child_value)
162         return True
163
164 Calendar()
165
166 class Event(CalDAV, osv.osv_memory):
167     _name = 'caldav.event'
168     __attribute__ = {
169         'class' : None, # Use: O-1, Type: TEXT,         Defines the access classification for a calendar  component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
170         'created' : None, # Use: O-1, Type: DATE-TIME,    Specifies the date and time that the calendar information  was created by the calendar user agent in the calendar store.
171         'description' : None, # Use: O-1, Type: TEXT,            Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
172         'dtstart' : None, # Use: O-1, Type: DATE-TIME,    Specifies when the calendar component begins.
173         'geo' : None, # Use: O-1, Type: FLOAT,        Specifies information related to the global position for the activity specified by a calendar component.
174         'last-mod' : None, # Use: O-1, Type: DATE-TIME        Specifies the date and time that the information associated with the calendar component was last revised in the calendar store.
175         'location' : None, # Use: O-1, Type: TEXT            Defines the intended venue for the activity defined by a calendar component.
176         'organizer' : None, # Use: O-1, Type: CAL-ADDRESS,  Defines the organizer for a calendar component.
177         'priority' : None, # Use: O-1, Type: INTEGER,      Defines the relative priority for a calendar component.
178         'dtstamp'  : None, # Use: O-1, Type: DATE-TIME,    Indicates the date/time that the instance of the iCalendar object was created.
179         'seq' : None, # Use: O-1, Type: INTEGER,      Defines the revision sequence number of the calendar component within a sequence of revision.
180         'status' : None, # Use: O-1, Type: TEXT,            Defines the overall status or confirmation for the calendar component.
181         'summary' : None, # Use: O-1, Type: TEXT,            Defines a short summary or subject for the calendar component.
182         'transp' : None, # Use: O-1, Type: TEXT,            Defines whether an event is transparent or not to busy time searches.
183         'uid' : None, # Use: O-1, Type: TEXT,            Defines the persistent, globally unique identifier for the calendar component.
184         'url' : None, # Use: O-1, Type: URL,          Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
185         'recurid' : None,
186         'attach' : None, # Use: O-n, Type: BINARY,       Provides the capability to associate a document object with a calendar component.
187         'attendee' : None, # Use: O-n, Type: CAL-ADDRESS,    Defines an "Attendee" within a calendar component.
188         'categories' : None, # Use: O-n,    Type: TEXT,            Defines the categories for a calendar component.
189         'comment' : None, # Use: O-n,    Type: TEXT,            Specifies non-processing information intended to provide a comment to the calendar user.
190         'contact' : None, # Use: O-n,    Type: TEXT,         Used to represent contact information or alternately a  reference to contact information associated with the calendar component.
191         'exdate'  : None, # Use: O-n,    Type: DATE-TIME,    Defines the list of date/time exceptions for a recurring calendar component.
192         'exrule'  : None, # Use: O-n,    Type: RECUR,        Defines a rule or repeating pattern for an exception to a recurrence set.
193         'rstatus' : None,
194         'related' : None, # Use: O-n,                     Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
195                                 #                               like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;RELATED=END:PT5M
196         'resources' : None, # Use: O-n, Type: TEXT,            Defines the equipment or resources anticipated for an activity specified by a calendar entity like RESOURCES:EASEL,PROJECTOR,VCR, LANGUAGE=fr:1 raton-laveur
197         'rdate' : None, # Use: O-n,    Type: DATE-TIME,    Defines the list of date/times for a recurrence set.
198         'rrule' : None, # Use: O-n,    Type: RECUR,        Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
199         'x-prop' : None,
200         'duration' : None, # Use: O-1,    Type: DURATION,        Specifies a positive duration of time.
201         'dtend' : None, # Use: O-1,    Type: DATE-TIME,    Specifies the date and time that a calendar component ends.
202     }
203
204     def get_recurrent_dates(self, rrulestring, exdate, startdate=None):
205         todate = parser.parse
206         if not startdate:
207             startdate = datetime.now()
208         else:
209             startdate = todate(startdate)
210         rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
211         for date in exdate:
212             datetime_obj = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
213 #            datetime_obj_utc = datetime_obj.replace(tzinfo=timezone('UTC'))
214             rset1._exdate.append(datetime_obj)
215         re_dates = rset1._iter()
216         recurrent_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), re_dates)
217         return recurrent_dates
218
219     def search(self, cr, uid, args, offset=0, limit=None, order=None,
220             context=None, count=False):
221         # put logic for recurrent event
222         # example : 123-20091111170822
223         pass
224
225     def create(self, cr, uid, vals, context={}):
226         # put logic for recurrent event
227         # example : 123-20091111170822
228         pass
229
230     def write(self, cr, uid, ids, vals, context={}):
231         # put logic for recurrent event
232 #        # example : 123-20091111170822
233         pass
234
235 Event()
236
237 class ToDo(CalDAV):
238     __attribute__ = {
239     }
240
241 class Journal(CalDAV):
242     __attribute__ = {
243     }
244
245 class FreeBusy(CalDAV):
246     __attribute__ = {
247     'contact' : None, # Use: O-1, Type: Text,         Represent contact information or alternately a  reference to contact information associated with the calendar component.
248     'dtstart' : None, # Use: O-1, Type: DATE-TIME,    Specifies when the calendar component begins.
249     'dtend' : None, # Use: O-1, Type: DATE-TIME,    Specifies the date and time that a calendar component ends.
250     'duration' : None, # Use: O-1, Type: DURATION,     Specifies a positive duration of time.
251     'dtstamp' : None, # Use: O-1, Type: DATE-TIME,    Indicates the date/time that the instance of the iCalendar object was created.
252     'organizer' : None, # Use: O-1, Type: CAL-ADDRESS,  Defines the organizer for a calendar component.
253     'uid' : None, # Use: O-1, Type: Text,         Defines the persistent, globally unique identifier for the calendar component.
254     'url' : None, # Use: O-1, Type: URL,          Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
255     'attendee' : None, # Use: O-n, Type: CAL-ADDRESS,  Defines an "Attendee" within a calendar component.
256     'comment' : None, # Use: O-n, Type: TEXT,         Specifies non-processing information intended to provide a comment to the calendar user.
257     'freebusy' : None, # Use: O-n, Type: PERIOD,       Defines one or more free or busy time intervals.
258     'rstatus' : None,
259     'X-prop' : None,
260     }
261
262 class Timezone(CalDAV):
263     __attribute__ = {
264     'tzid' : None, # Use: R-1, Type: Text,         Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
265     'last-mod' : None, # Use: O-1, Type: DATE-TIME,    Specifies the date and time that the information associated with the calendar component was last revised in the calendar store.
266     'tzurl' : None, # Use: O-1, Type: URI,          Provides a means for a VTIMEZONE component to point to a network location that can be used to retrieve an up-to-date version of itself.
267     'standardc' :           # Use: R-n,
268         {'tzprop' : None, }, # Use: R-1,
269     'daylightc' :           # Use: R-n, Type: Text,
270         {'tzprop' : None, }, # Use: R-1,
271     'x-prop' : None, # Use: O-n, Type: Text,
272     }
273     def import_ical(self, cr, uid, ical_data):
274         for val in ical_data.getChildren():
275             if self.__attribute__.has_key(val.name.lower()):
276                 self.__attribute__[val.name] = val.value
277
278 class Alarm(CalDAV):
279     __attribute__ = {
280
281     'action' : None, # Use: R-1, Type: Text,        defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
282     'description' : None, #      Type: Text,        Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property. Use:- R-1 for DISPLAY,Use:- R-1 for EMAIL,Use:- R-1 for PROCEDURE
283     'summary' : None, # Use: R-1, Type: Text        Which contains the text to be used as the message subject. Use for EMAIL
284     'attendee' : None, # Use: R-n, Type: CAL-ADDRESS,    Contain the email address of attendees to receive the message. It can also include one or more. Use for EMAIL
285     'trigger' : None, # Use: R-1, Type: DURATION,    The "TRIGGER" property specifies a duration prior to the start of an event or a to-do. The "TRIGGER" edge may be explicitly set to be relative to the "START" or "END" of the event or to-do with the "RELATED" parameter of the "TRIGGER" property. The "TRIGGER" property value type can alternatively be set to an absolute calendar date and time of day value. Use for all action like AUDIO, DISPLAY, EMAIL and PROCEDURE
286     'duration' : None, #           Type: DURATION,    Duration' and 'repeat' are both optional, and MUST NOT occur more than once each, but if one occurs, so MUST the other. Use:- 0-1 for AUDIO, EMAIL and PROCEDURE, Use:- 0-n for DISPLAY
287     'repeat' : None, #           Type: INTEGER,    Duration' and 'repeat' are both optional, and MUST NOT occur more than once each, but if one occurs, so MUST the other. Use:- 0-1 for AUDIO, EMAIL and PROCEDURE, Use:- 0-n for DISPLAY
288     'attach' : None, # Use:- O-n : which MUST point to a sound resource, which is rendered when the alarm is triggered for AUDIO, Use:- O-n : which are intended to be sent as message attachments for EMAIL, Use:- R-1:which MUST point to a procedure resource, which is invoked when the alarm is triggered for PROCEDURE.
289     'x-prop' : None,
290     }
291
292     def import_ical(self, cr, uid, ical_data):
293         for val in ical_data.getChildren():
294             if self.__attribute__.has_key(val.name.lower()):
295                 self.__attribute__[val.name] = val.value
296
297 class Attendee(CalDAV):
298 #  Also inherit osv_memory
299     _name = 'caldav.attendee'
300     __attribute__ = {
301     'cutype' : None, # Use: 0-1    Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
302     'member' : None, # Use: 0-1    Specify the group or list membership of the calendar user specified by the property.
303     'role' : None, # Use: 0-1    Specify the participation role for the calendar user specified by the property like "CHAIR"/"REQ-PARTICIPANT"/"OPT-PARTICIPANT"/"NON-PARTICIPANT"
304     'partstat' : None, # Use: 0-1    Specify the participation status for the calendar user specified by the property. like use for VEVENT :- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED", use for VTODO :-"NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED"/"COMPLETED"/"IN-PROCESS" and use for VJOURNAL :- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED".
305     'rsvp' : None, # Use: 0-1    Specify whether there is an expectation of a favor of a reply from the calendar user specified by the property value like TRUE / FALSE.
306     'delegated-to' : None, # Use: 0-1    Specify the calendar users to whom the calendar user specified by the property has delegated participation.
307     'delegated-from' : None, # Use: 0-1    Specify the calendar users that have delegated their participation to the calendar user specified by the property.
308     'sent-by' : None, # Use: 0-1    Specify the calendar user that is acting on behalf of the calendar user specified by the property.
309     'cn' : None, # Use: 0-1    Specify the common name to be associated with the calendar user specified by the property.
310     'dir' : None, # Use: 0-1    Specify reference to a directory entry associated with the calendar user specified by the property.
311     'language' : None, # Use: 0-1    Specify the language for text values in a property or property parameter.
312     }
313
314     def import_ical(self, cr, uid, ical_data):
315         if ical_data.value:
316             self.__attribute__['sent-by'] = ical_data.value
317         for key, val in ical_data.params.items():
318             if self.__attribute__.has_key(key.lower()):
319                 if type(val) == type([]):
320                     self.__attribute__[key] = val[0]
321                 else:
322                     self.__attribute__[key] = val
323         return
324
325 Attendee()