84d9a744fe892c5d663417f2195ccff0249190ff
[odoo/odoo.git] / addons / base_calendar / base_calendar.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.     
19 #
20 ##############################################################################
21
22 from datetime import datetime, timedelta
23 from dateutil import parser
24 from dateutil.rrule import *
25 from osv import osv, fields
26 from tools.translate import _
27 import base64
28 import pooler
29 import re
30 import vobject
31
32 # O-1  Optional and can come only once
33 # O-n  Optional and can come more than once
34 # R-1  Required and can come only once
35 # R-n  Required and can come more than once
36
37 def uid2openobjectid(cr, uidval, oomodel, rdate):
38     __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
39     wematch = __rege.match(uidval.encode('utf8'))
40     if not wematch:
41         return (False, None)
42     else:
43         model, id, dbname = wematch.groups()
44         model_obj = pooler.get_pool(cr.dbname).get(model)
45         if (not model == oomodel) or (not dbname == cr.dbname):
46             return (False, None)
47         qry = 'select distinct(id) from %s' % model_obj._table
48         if rdate:
49             qry += " where recurrent_id='%s'" % (rdate)
50             cr.execute(qry)
51             r_id = cr.fetchone()
52             if r_id:
53                 return (id, r_id[0])
54         cr.execute(qry)
55         ids = map(lambda x: str(x[0]), cr.fetchall())
56         if id in ids:
57             return (id, None)
58         return False
59
60 def openobjectid2uid(cr, uidval, oomodel):
61     value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
62     return value
63
64 def get_attribute_mapping(cr, uid, calname, context={}):
65         if not context:
66             context = {}
67         pool = pooler.get_pool(cr.dbname)
68         field_obj = pool.get('basic.calendar.fields')
69         type_obj = pool.get('basic.calendar.lines')
70         type_id = type_obj.search(cr, uid, [('object_id.model', '=', context.get('model'))])
71         fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])])
72         res = {}
73         for field in field_obj.browse(cr, uid, fids):
74             attr = field.name.name
75             res[attr] = {}
76             res[attr]['field'] = field.field_id.name
77             res[attr]['type'] = field.field_id.ttype
78             if field.fn == 'hours':
79                 res[attr]['type'] = "timedelta"
80             if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
81                 res[attr]['object'] = field.field_id.relation
82             elif res[attr]['type'] in ('selection') and field.mapping:
83                 res[attr]['mapping'] = eval(field.mapping)
84         if not res.get('uid', None):
85             res['uid'] = {}
86             res['uid']['field'] = 'id'
87             res[attr]['type'] = "integer"
88         return res
89
90 def map_data(cr, uid, obj):
91     vals = {}
92     for map_dict in obj.__attribute__:
93         map_val = obj.ical_get(map_dict, 'value')
94         field = obj.ical_get(map_dict, 'field')
95         field_type = obj.ical_get(map_dict, 'type')
96         if field:
97             if field_type == 'selection':
98                 if not map_val:
99                     continue
100                 mapping = obj.__attribute__[map_dict].get('mapping', False)
101                 if mapping:
102                     map_val = mapping[map_val.lower()]
103                 else:
104                     map_val = map_val.lower()
105             if field_type == 'many2many':
106                 ids = []
107                 if not map_val:
108                     vals[field] = ids
109                     continue
110                 model = obj.__attribute__[map_dict].get('object', False)
111                 modobj = obj.pool.get(model)
112                 for map_vall in map_val:
113                     id = modobj.create(cr, uid, map_vall)
114                     ids.append(id)
115                 vals[field] = [(6, 0, ids)]
116                 continue
117             if field_type == 'many2one':
118                 id = None
119                 if not map_val or not isinstance(map_val, dict):
120                     vals[field] = id
121                     continue
122                 model = obj.__attribute__[map_dict].get('object', False)
123                 modobj = obj.pool.get(model)
124                 id = modobj.create(cr, uid, map_val)
125                 vals[field] = id
126                 continue
127             if field_type == 'timedelta':
128                 if map_val:
129                     vals[field] = (map_val.seconds/float(86400) + map_val.days)
130             if map_val:
131                 vals[field] = map_val
132     return vals
133
134 class CalDAV(object):
135     __attribute__ = {}
136
137     def get_recurrent_dates(self, rrulestring, exdate, startdate=None):
138         if not startdate:
139             startdate = datetime.now()
140         rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
141
142         for date in exdate:
143             datetime_obj = todate(date)
144             rset1._exdate.append(datetime_obj)
145         re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
146         return re_dates
147
148     def ical_set(self, name, value, type):
149         if name in self.__attribute__ and self.__attribute__[name]:
150             self.__attribute__[name][type] = value
151         return True
152
153     def ical_get(self, name, type):
154         if self.__attribute__.get(name):
155             val = self.__attribute__.get(name).get(type, None)
156             valtype =  self.__attribute__.get(name).get('type', None)
157             if type == 'value':
158                 if valtype and valtype == 'datetime' and val:
159                     if isinstance(val, list):
160                         val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
161                     else:
162                         val = val.strftime('%Y-%m-%d %H:%M:%S')
163             return  val
164         else:
165             return  self.__attribute__.get(name, None)
166
167     def ical_reset(self, type):
168         for name in self.__attribute__:
169             if self.__attribute__[name]:
170                 self.__attribute__[name][type] = None
171         return True
172     
173     def parse_ics(self, cr, uid, child):
174         att_data = []
175         for cal_data in child.getChildren():
176             if cal_data.name.lower() == 'attendee':
177                 attendee = self.pool.get('basic.calendar.attendee')
178                 att_data.append(attendee.import_cal(cr, uid, cal_data))
179                 self.ical_set(cal_data.name.lower(), att_data, 'value')
180                 continue
181             if cal_data.name.lower() == 'valarm':
182                 alarm = self.pool.get('basic.calendar.alarm')
183                 vals = alarm.import_cal(cr, uid, cal_data)
184                 self.ical_set(cal_data.name.lower(), vals, 'value')
185                 continue
186             if cal_data.name.lower() in self.__attribute__:
187                 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
188         vals = map_data(cr, uid, self)
189         return vals
190
191     def create_ics(self, cr, uid, datas, name, ical, context=None):
192         if not datas:
193             model = context.get('model', None)
194             war_str = "No data available" + (model and " for " + model) or ""
195             raise osv.except_osv(_('Warning !'), _(war_str))
196         for data in datas:
197             vevent = ical.add(name)
198             for field in self.__attribute__.keys():
199                 map_field = self.ical_get(field, 'field')
200                 map_type = self.ical_get(field, 'type')
201                 if map_field in data.keys():
202                     if field == 'uid':
203                         model = context.get('model', None)
204                         if not model:
205                             continue
206                         uidval = openobjectid2uid(cr, data[map_field], model)
207                         model_obj = self.pool.get(model)
208                         r_ids = []
209                         if model_obj._columns.get('recurrent_uid', None):
210                             cr.execute('select id from %s  where recurrent_uid=%s' 
211                                            % (model_obj._table, data[map_field]))
212                             r_ids = map(lambda x: x[0], cr.fetchall())
213                         if r_ids: 
214                             rdata = self.pool.get(model).read(cr, uid, r_ids)
215                             rcal = self.export_cal(cr, uid, rdata, context=context)
216                             for revents in rcal.contents['vevent']:
217                                 ical.contents['vevent'].append(revents)
218                         if data.get('recurrent_uid', None):
219                             uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
220                         vevent.add('uid').value = uidval
221                     elif field == 'attendee' and data[map_field]:
222                         model = self.__attribute__[field].get('object', False)
223                         attendee_obj = self.pool.get('basic.calendar.attendee')
224                         vevent = attendee_obj.export_cal(cr, uid, model, \
225                                      data[map_field], vevent, context=context)
226                     elif field == 'valarm' and data[map_field]:
227                         model = self.__attribute__[field].get('object', False)
228                         alarm_obj = self.pool.get('basic.calendar.alarm')
229                         vevent = alarm_obj.export_cal(cr, uid, model, \
230                                     data[map_field][0], vevent, context=context)
231                     elif data[map_field]:
232                         if map_type in ("char", "text"):
233                             vevent.add(field).value = str(data[map_field])
234                         elif map_type in ('datetime', 'date') and data[map_field]:
235                             if field in ('exdate'):
236                                 vevent.add(field).value = [parser.parse(data[map_field])]
237                             else:
238                                 vevent.add(field).value = parser.parse(data[map_field])
239                         elif map_type == "timedelta":
240                             vevent.add(field).value = timedelta(hours=data[map_field])
241                         elif map_type == "many2one":
242                             vevent.add(field).value = data.get(map_field)[1]
243                         elif map_type in ("float", "integer"):
244                             vevent.add(field).value = str(data.get(map_field))
245                         elif map_type == "selection":
246                             if not self.ical_get(field, 'mapping'):
247                                 vevent.add(field).value = (data[map_field]).upper()
248                             else:
249                                 for key1, val1 in self.ical_get(field, 'mapping').items():
250                                     if val1 == data[map_field]:
251                                         vevent.add(field).value = key1
252         return vevent
253     
254     def check_import(self, cr, uid, vals, context={}):
255         ids = []
256         model_obj = self.pool.get(context.get('model'))
257         try:
258             for val in vals:
259                 exists, r_id = uid2openobjectid(cr, val['id'], context.get('model'), \
260                                                                  val.get('recurrent_id'))
261                 if val.has_key('create_date'): val.pop('create_date')
262                 val.pop('id')
263                 if exists and r_id:
264                     val.update({'recurrent_uid': exists})
265                     model_obj.write(cr, uid, [r_id], val)
266                     ids.append(r_id)
267                 elif exists:
268                     model_obj.write(cr, uid, [exists], val)
269                     ids.append(exists)
270                 else:
271                     event_id = model_obj.create(cr, uid, val)
272                     ids.append(event_id)
273         except Exception, e:
274             raise osv.except_osv(('Error !'), (str(e)))
275         return ids
276
277     def export_cal(self, cr, uid, datas, vobj=None, context={}):
278         try:
279             self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
280             ical = vobject.iCalendar()
281             self.create_ics(cr, uid, datas, vobj, ical, context=context)
282             return ical
283         except Exception, e:
284             raise osv.except_osv(('Error !'), (str(e)))
285
286     def import_cal(self, cr, uid, content, data_id=None, context=None):
287         ical_data = base64.decodestring(content)
288         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
289         parsedCal = vobject.readOne(ical_data)
290         att_data = []
291         res = []
292         for child in parsedCal.getChildren():
293             if child.name.lower() in ('vevent', 'vtodo'):
294                 vals = self.parse_ics(cr, uid, child)
295             else:
296                 vals = {}
297                 continue
298             if vals: res.append(vals)
299             self.ical_reset('value')
300         return res
301
302
303 class Calendar(CalDAV, osv.osv):
304     _name = 'basic.calendar'
305     _description = 'Calendar'
306     _calname = 'calendar'
307
308     __attribute__ = {
309         'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
310         'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
311                            #             or the minimum and maximum range of the iCalendar specification
312                            #             that is required in order to interpret the iCalendar object.
313         'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
314         'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
315         'vevent': None, # Use: O-n, Type: Collection of Event class
316         'vtodo': None, # Use: O-n, Type: Collection of ToDo class
317         'vjournal': None, # Use: O-n, Type: Collection of Journal class
318         'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
319         'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
320     }
321     _columns = {
322             'name': fields.char("Name", size=64), 
323             'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'), 
324             'active': fields.boolean('Active'), 
325     }
326
327     _defaults = {
328                 'active': lambda *a: True,
329                  }
330
331     def export_cal(self, cr, uid, datas, vobj='vevent', context={}):
332         try:
333             cal = self.browse(cr, uid, datas[0])
334             ical = vobject.iCalendar()
335             for line in cal.line_ids:
336                 if line.name in ('alarm', 'attendee'):
337                     continue
338                 mod_obj = self.pool.get(line.object_id.model)
339                 data_ids = mod_obj.search(cr, uid, eval(line.domain), context=context)
340                 datas = mod_obj.read(cr, uid, data_ids, context=context)
341                 context.update({'model': line.object_id.model})
342                 self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
343                 self.create_ics(cr, uid, datas, line.name, ical, context=context)
344             return ical.serialize()
345         except Exception, e:
346             raise osv.except_osv(('Error !'), (str(e)))
347
348     def import_cal(self, cr, uid, content, data_id=None, context=None):
349         ical_data = base64.decodestring(content)
350         parsedCal = vobject.readOne(ical_data)
351         if not data_id:
352             data_id = self.search(cr, uid, [])[0]
353         cal = self.browse(cr, uid, data_id)
354         cal_children = {}
355         count = 0
356         for line in cal.line_ids:
357             cal_children[line.name] = line.object_id.model
358         for child in parsedCal.getChildren():
359             if child.name.lower() in cal_children:
360                 context.update({'model': cal_children[child.name.lower()]})
361                 self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
362                 val = self.parse_ics(cr, uid, child)
363                 obj = self.pool.get(cal_children[child.name.lower()])
364                 if hasattr(obj, 'check_import'):
365                     obj.check_import(cr, uid, [val], context=context)
366                 else:
367                     self.check_import(cr, uid, [val], context=context)
368         return {}
369
370 Calendar()
371     
372 class basic_calendar_line(osv.osv):
373     _name = 'basic.calendar.lines'
374     _description = 'Calendar Lines'
375     _columns = {
376             'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
377                                     ('alarm', 'Alarm'), \
378                                     ('attendee', 'Attendee')], \
379                                     string="Type", size=64), 
380             'object_id': fields.many2one('ir.model', 'Object'), 
381             'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
382                                        required=True, ondelete='cascade'), 
383             'domain': fields.char('Domain', size=124), 
384             'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
385     }   
386
387     _defaults = {
388         'domain': lambda *a: '[]',
389     }
390     
391 basic_calendar_line()
392
393 class basic_calendar_attribute(osv.osv):
394     _name = 'basic.calendar.attributes'
395     _description = 'Calendar attributes'
396     _columns = {        
397         'name': fields.char("Name", size=64, required=True),
398         'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
399                                     ('alarm', 'Alarm'), \
400                                     ('attendee', 'Attendee')], \
401                                     string="Type", size=64, required=True),        
402     }
403
404 basic_calendar_attribute()
405
406 class basic_calendar_fields(osv.osv):
407     _name = 'basic.calendar.fields'
408     _description = 'Calendar fields'
409
410     _columns = {
411         'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
412         'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
413         'type_id': fields.many2one('basic.calendar.lines', 'Type', \
414                                    required=True, ondelete='cascade'),
415         'expr': fields.char("Expression", size=64),
416         'fn': fields.selection( [('field', 'Use the field'),
417                         ('const', 'Expression as constant'),
418                         ('hours', 'Interval in hours'),
419                         ],'Function'), 
420         'mapping': fields.text('Mapping'), 
421     }
422
423     _defaults = {
424         'fn': lambda *a: 'field',
425     }
426
427 basic_calendar_fields()
428
429 class Event(CalDAV, osv.osv_memory):
430     _name = 'basic.calendar.event'
431     _calname = 'vevent'
432     __attribute__ = {
433         'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar  component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
434         '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.
435         'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
436         'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
437         'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
438         '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.
439         'location': None, # Use: O-1, Type: TEXT            Defines the intended venue for the activity defined by a calendar component.
440         'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
441         'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
442         'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
443         'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
444         'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
445         'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
446         'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
447         'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
448         'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
449         'recurid': None, 
450         'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
451         'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
452         'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
453         'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
454         'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a  reference to contact information associated with the calendar component.
455         'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
456         'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
457         'rstatus': None, 
458         'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
459                                 #  like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
460         '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
461         'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
462         'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
463         'x-prop': None, 
464         'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
465         'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
466     }
467     def export_cal(self, cr, uid, datas, vobj='vevent', context={}):
468         return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
469
470 Event()
471
472 class ToDo(CalDAV, osv.osv_memory):
473     _name = 'basic.calendar.todo'
474     _calname = 'vtodo'
475
476     __attribute__ = {
477                 'class': None, 
478                 'completed': None, 
479                 'created': None, 
480                 'description': None, 
481                 'dtstamp': None, 
482                 'dtstart': None, 
483                 'duration': None, 
484                 'due': None, 
485                 'geo': None, 
486                 'last-mod ': None, 
487                 'location': None, 
488                 'organizer': None, 
489                 'percent': None, 
490                 'priority': None, 
491                 'recurid': None, 
492                 'seq': None, 
493                 'status': None, 
494                 'summary': None, 
495                 'uid': None, 
496                 'url': None, 
497                 'attach': None, 
498                 'attendee': None, 
499                 'categories': None, 
500                 'comment': None, 
501                 'contact': None, 
502                 'exdate': None, 
503                 'exrule': None, 
504                 'rstatus': None, 
505                 'related': None, 
506                 'resources': None, 
507                 'rdate': None, 
508                 'rrule': None, 
509             }
510
511     def export_cal(self, cr, uid, datas, vobj='vevent', context={}):
512         return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
513
514 ToDo()
515
516 class Journal(CalDAV):
517     __attribute__ = {
518     }
519
520 class FreeBusy(CalDAV):
521     __attribute__ = {
522     'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a  reference to contact information associated with the calendar component.
523     'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
524     'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
525     'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
526     'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
527     'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
528     'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
529     'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
530     'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
531     'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
532     'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
533     'rstatus': None, 
534     'X-prop': None, 
535     }
536
537
538 class Timezone(CalDAV):
539     __attribute__ = {
540     'tzid': None, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
541     '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.
542     '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.
543     'standardc': {'tzprop': None}, # Use: R-1,
544     'daylightc': {'tzprop': None}, # Use: R-1,
545     'x-prop': None, # Use: O-n, Type: Text,
546     }
547
548
549 class Alarm(CalDAV, osv.osv_memory):
550     _name = 'basic.calendar.alarm'
551     _calname = 'alarm'
552
553     __attribute__ = {
554     'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
555     '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
556     'summary': None, # Use: R-1, Type: Text        Which contains the text to be used as the message subject. Use for EMAIL
557     '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
558     '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
559     '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
560     '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
561     '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.
562     'x-prop': None, 
563     }
564
565     def export_cal(self, cr, uid, model, alarm_id, vevent, context={}):
566         valarm = vevent.add('valarm')
567         alarm_object = self.pool.get(model)
568         alarm_data = alarm_object.read(cr, uid, alarm_id, [])
569
570         # Compute trigger data
571         interval = alarm_data['trigger_interval']
572         occurs = alarm_data['trigger_occurs']
573         duration = (occurs == 'after' and alarm_data['trigger_duration']) \
574                                         or -(alarm_data['trigger_duration'])
575         related = alarm_data['trigger_related']
576         trigger = valarm.add('TRIGGER')
577         trigger.params['related'] = [related.upper()]
578         if interval == 'days':
579             delta = timedelta(days=duration)
580         if interval == 'hours':
581             delta = timedelta(hours=duration)
582         if interval == 'minutes':
583             delta = timedelta(minutes=duration)
584         trigger.value = delta
585
586         # Compute other details
587         valarm.add('DESCRIPTION').value = alarm_data['name']
588         valarm.add('ACTION').value = alarm_data['action']
589         return vevent
590
591     def import_cal(self, cr, uid, ical_data):
592         for child in ical_data.getChildren():
593             if child.name.lower() == 'trigger':
594                 seconds = child.value.seconds
595                 days = child.value.days
596                 diff = (days * 86400) +  seconds
597                 interval = 'days'
598                 related = 'before'
599                 if not seconds:
600                     duration = abs(days)
601                     related = days > 0 and 'after' or 'before'
602                 elif (abs(diff) / 3600) == 0:
603                     duration = abs(diff / 60)
604                     interval = 'minutes'
605                     related = days >= 0 and 'after' or 'before'
606                 else:
607                     duration = abs(diff / 3600)
608                     interval = 'hours'
609                     related = days >= 0 and 'after' or 'before'
610                 self.ical_set('trigger_interval', interval, 'value')
611                 self.ical_set('trigger_duration', duration, 'value')
612                 self.ical_set('trigger_occurs', related.lower(), 'value')
613                 if child.params:
614                     if child.params.get('related'):
615                         self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
616             else:
617                 self.ical_set(child.name.lower(), child.value.lower(), 'value')
618         vals = map_data(cr, uid, self)
619         return vals
620
621 Alarm()
622
623 class Attendee(CalDAV, osv.osv_memory):
624     _name = 'basic.calendar.attendee'
625     _calname = 'attendee'
626
627     __attribute__ = {
628     'cutype': None, # Use: 0-1    Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
629     'member': None, # Use: 0-1    Specify the group or list membership of the calendar user specified by the property.
630     '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"
631     '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".
632     '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.
633     'delegated-to': None, # Use: 0-1    Specify the calendar users to whom the calendar user specified by the property has delegated participation.
634     'delegated-from': None, # Use: 0-1    Specify the calendar users that have delegated their participation to the calendar user specified by the property.
635     'sent-by': None, # Use: 0-1    Specify the calendar user that is acting on behalf of the calendar user specified by the property.
636     'cn': None, # Use: 0-1    Specify the common name to be associated with the calendar user specified by the property.
637     'dir': None, # Use: 0-1    Specify reference to a directory entry associated with the calendar user specified by the property.
638     'language': None, # Use: 0-1    Specify the language for text values in a property or property parameter.
639     }
640
641     def import_cal(self, cr, uid, ical_data):
642         for para in ical_data.params:
643             if para.lower() == 'cn':
644                 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
645                         ical_data.value, 'value')
646             else:
647                 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
648         if not ical_data.params.get('CN'):
649             self.ical_set('cn', ical_data.value, 'value')
650         vals = map_data(cr, uid, self)
651         return vals
652
653     def export_cal(self, cr, uid, model, attendee_ids, vevent, context={}):
654         attendee_object = self.pool.get(model)
655         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
656         for attendee in attendee_object.read(cr, uid, attendee_ids, []):
657             attendee_add = vevent.add('attendee')
658             cn_val = ''
659             for a_key, a_val in self.__attribute__.items():
660                 if attendee[a_val['field']] and a_val['field'] != 'cn':
661                     if a_val['type'] == 'text':
662                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
663                     elif a_val['type'] == 'boolean':
664                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
665                 if a_val['field'] == 'cn':
666                     cn_val = [str(attendee[a_val['field']])]
667             attendee_add.params['CN'] = cn_val 
668         return vevent
669
670 Attendee()
671
672
673 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: