1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from datetime import datetime, timedelta
23 from dateutil import parser
24 from dateutil.rrule import *
30 # O-1 Optional and can come only once
31 # O-n Optional and can come more than once
32 # R-1 Required and can come only once
33 # R-n Required and can come more than once
35 def map_data(cr, uid, obj):
37 for map_dict in obj.__attribute__:
38 map_val = obj.ical_get(map_dict, 'value')
39 field = obj.ical_get(map_dict, 'field')
40 field_type = obj.ical_get(map_dict, 'type')
42 if field_type == 'selection':
45 mapping =obj.__attribute__[map_dict].get('mapping', False)
47 map_val = mapping[map_val.lower()]
49 map_val = map_val.lower()
50 if field_type == 'many2many':
55 model = obj.__attribute__[map_dict].get('object', False)
56 modobj = obj.pool.get(model)
57 for map_vall in map_val:
58 id = modobj.create(cr, uid, map_vall)
60 vals[field] = [(6, 0, ids)]
62 if field_type == 'many2one':
64 if not map_val or not isinstance(map_val, dict):
67 model = obj.__attribute__[map_dict].get('object', False)
68 modobj = obj.pool.get(model)
69 id = modobj.create(cr, uid, map_val)
79 def get_recurrent_dates(self, rrulestring, exdate, startdate=None):
81 startdate = datetime.now()
82 rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
85 datetime_obj = todate(date)
86 rset1._exdate.append(datetime_obj)
87 re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
90 def ical_set(self, name, value, type):
91 if name in self.__attribute__ and self.__attribute__[name]:
92 self.__attribute__[name][type] = value
95 def ical_get(self, name, type):
96 if self.__attribute__.get(name):
97 val = self.__attribute__.get(name).get(type, None)
98 valtype = self.__attribute__.get(name).get('type', None)
100 if valtype and valtype=='datetime' and val:
101 if isinstance(val, list):
102 val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
104 val = val.strftime('%Y-%m-%d %H:%M:%S')
105 if valtype and valtype=='integer' and val:
109 return self.__attribute__.get(name, None)
111 def ical_reset(self, type):
112 for name in self.__attribute__:
113 if self.__attribute__[name]:
114 self.__attribute__[name][type] = None
117 def export_ical(self, cr, uid, datas, vobj=None, context={}):
118 ical = vobject.iCalendar()
120 vevent = ical.add(vobj)
121 for field in self.__attribute__.keys():
122 map_field = self.ical_get(field, 'field')
123 map_type = self.ical_get(field, 'type')
124 if map_field in data.keys():
126 model = context.get('model', None)
129 uidval = common.openobjectid2uid(cr, data[map_field], model)
130 vevent.add('uid').value = uidval
131 elif field == 'attendee' and data[map_field]:
132 attendee_obj = self.pool.get('basic.calendar.attendee')
133 vevent = attendee_obj.export_ical(cr, uid, data[map_field], vevent, context=context)
134 elif field == 'valarm' and data[map_field]:
135 alarm_obj = self.pool.get('basic.calendar.alarm')
136 vevent = alarm_obj.export_ical(cr, uid, data[map_field][0], vevent, context=context)
137 elif data[map_field]:
138 if map_type == "text":
139 vevent.add(field).value = str(data[map_field])
140 elif map_type == 'datetime' and data[map_field]:
141 if field in ('exdate'):
142 vevent.add(field).value = [parser.parse(data[map_field])]
144 vevent.add(field).value = parser.parse(data[map_field])
145 elif map_type == "timedelta":
146 vevent.add(field).value = timedelta(hours=data[map_field])
147 elif map_type == "many2one":
148 vevent.add(field).value = [data.get(map_field)[1]]
149 if self.__attribute__.get(field).has_key('mapping'):
150 for key1, val1 in self.ical_get(field, 'mapping').items():
151 if val1 == data[map_field]:
152 vevent.add(field).value = key1
155 def import_ical(self, cr, uid, ical_data):
156 parsedCal = vobject.readOne(ical_data)
159 for child in parsedCal.getChildren():
160 for cal_data in child.getChildren():
161 if cal_data.name.lower() == 'attendee':
162 attendee = self.pool.get('basic.calendar.attendee')
163 att_data.append(attendee.import_ical(cr, uid, cal_data))
164 self.ical_set(cal_data.name.lower(), att_data, 'value')
166 if cal_data.name.lower() == 'valarm':
167 alarm = self.pool.get('basic.calendar.alarm')
168 vals = alarm.import_ical(cr, uid, cal_data)
169 self.ical_set(cal_data.name.lower(), vals, 'value')
171 if cal_data.name.lower() in self.__attribute__:
172 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
173 if child.name.lower() in ('vevent', 'vtodo'):
174 vals = map_data(cr, uid, self)
178 if vals: res.append(vals)
179 self.ical_reset('value')
183 class Calendar(CalDAV, osv.osv_memory):
184 _name = 'basic.calendar'
186 'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
187 'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
188 # or the minimum and maximum range of the iCalendar specification
189 # that is required in order to interpret the iCalendar object.
190 'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
191 'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
192 'vevent': None, # Use: O-n, Type: Collection of Event class
193 'vtodo': None, # Use: O-n, Type: Collection of ToDo class
194 'vjournal': None, # Use: O-n, Type: Collection of Journal class
195 'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
196 'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
202 class Event(CalDAV, osv.osv_memory):
203 _name = 'basic.calendar.event'
205 'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
206 '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.
207 'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
208 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
209 'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
210 '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.
211 'location': None, # Use: O-1, Type: TEXT Defines the intended venue for the activity defined by a calendar component.
212 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
213 'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
214 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
215 'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
216 'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
217 'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
218 'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
219 'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
220 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
222 'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
223 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
224 'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
225 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
226 'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a reference to contact information associated with the calendar component.
227 'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
228 'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
230 'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
231 # like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
232 '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
233 'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
234 'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
236 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
237 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
239 def export_ical(self, cr, uid, datas, context={}):
240 return super(Event, self).export_ical(cr, uid, datas, 'vevent', context=context)
244 class ToDo(CalDAV, osv.osv_memory):
245 _name = 'basic.calendar.todo'
282 def export_ical(self, cr, uid, datas, context={}):
283 return super(ToDo, self).export_ical(cr, uid, datas, 'vtodo', context=context)
287 class Journal(CalDAV):
291 class FreeBusy(CalDAV):
293 'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a reference to contact information associated with the calendar component.
294 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
295 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
296 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
297 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
298 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
299 'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
300 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
301 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
302 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
303 'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
309 class Timezone(CalDAV):
311 'tzid': None, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
312 '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.
313 '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.
314 'standardc': {'tzprop': None}, # Use: R-1,
315 'daylightc': {'tzprop': None}, # Use: R-1,
316 'x-prop': None, # Use: O-n, Type: Text,
320 class Alarm(CalDAV, osv.osv_memory):
321 _name = 'basic.calendar.alarm'
323 'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
324 '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
325 'summary': None, # Use: R-1, Type: Text Which contains the text to be used as the message subject. Use for EMAIL
326 '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
327 '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
328 '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
329 '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
330 '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.
334 def export_ical(self, cr, uid, alarm_id, vevent, context={}):
335 valarm = vevent.add('valarm')
336 alarm_object = self.pool.get('calendar.alarm')
337 alarm_data = alarm_object.read(cr, uid, alarm_id, [])
339 # Compute trigger data
340 interval = alarm_data['trigger_interval']
341 occurs = alarm_data['trigger_occurs']
342 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
343 or -(alarm_data['trigger_duration'])
344 related = alarm_data['trigger_related']
345 trigger = valarm.add('TRIGGER')
346 trigger.params['related'] = [related.upper()]
347 if interval == 'days':
348 delta = timedelta(days=duration)
349 if interval == 'hours':
350 delta = timedelta(hours=duration)
351 if interval == 'minutes':
352 delta = timedelta(minutes=duration)
353 trigger.value = delta
355 # Compute other details
356 valarm.add('DESCRIPTION').value = alarm_data['name']
357 valarm.add('ACTION').value = alarm_data['action']
360 def import_ical(self, cr, uid, ical_data):
361 for child in ical_data.getChildren():
362 if child.name.lower() == 'trigger':
363 seconds = child.value.seconds
364 days = child.value.days
365 diff = (days * 86400) + seconds
370 related = days>0 and 'after' or 'before'
371 elif (abs(diff) / 3600) == 0:
372 duration = abs(diff / 60)
374 related = days>=0 and 'after' or 'before'
376 duration = abs(diff / 3600)
378 related = days>=0 and 'after' or 'before'
379 self.ical_set('trigger_interval', interval, 'value')
380 self.ical_set('trigger_duration', duration, 'value')
381 self.ical_set('trigger_occurs', related.lower(), 'value')
383 if child.params.get('related'):
384 self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
386 self.ical_set(child.name.lower(), child.value.lower(), 'value')
387 vals = map_data(cr, uid, self)
392 class Attendee(CalDAV, osv.osv_memory):
393 _name = 'basic.calendar.attendee'
395 'cutype': None, # Use: 0-1 Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
396 'member': None, # Use: 0-1 Specify the group or list membership of the calendar user specified by the property.
397 '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"
398 '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".
399 '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.
400 'delegated-to': None, # Use: 0-1 Specify the calendar users to whom the calendar user specified by the property has delegated participation.
401 'delegated-from': None, # Use: 0-1 Specify the calendar users that have delegated their participation to the calendar user specified by the property.
402 'sent-by': None, # Use: 0-1 Specify the calendar user that is acting on behalf of the calendar user specified by the property.
403 'cn': None, # Use: 0-1 Specify the common name to be associated with the calendar user specified by the property.
404 'dir': None, # Use: 0-1 Specify reference to a directory entry associated with the calendar user specified by the property.
405 'language': None, # Use: 0-1 Specify the language for text values in a property or property parameter.
408 def import_ical(self, cr, uid, ical_data):
409 for para in ical_data.params:
410 if para.lower() == 'cn':
411 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
412 ical_data.value, 'value')
414 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
415 if not ical_data.params.get('CN'):
416 self.ical_set('cn', ical_data.value, 'value')
417 vals = map_data(cr, uid, self)
420 def export_ical(self, cr, uid, attendee_id, vevent, context={}):
421 attendee_object = self.pool.get('calendar.attendee')
422 for attendee in attendee_object.read(cr, uid, attendee_id, []):
423 attendee_add = vevent.add('attendee')
424 for a_key, a_val in attendee_object.__attribute__.items():
425 if attendee[a_val['field']]:
426 if a_val['type'] == 'text':
427 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
428 elif a_val['type'] == 'boolean':
429 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
435 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: