[FIX]: caldav: Minor fix for import
[odoo/odoo.git] / addons / caldav / 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 math
28 import pooler
29 import pytz
30 import re
31 import tools
32 import time
33 from caldav_node import res_node_calendar
34
35 try:
36     import vobject
37 except ImportError:
38     raise osv.except_osv('vobject Import Error!','Please install python-vobject \
39                      from http://vobject.skyhouseconsulting.com/')
40
41 # O-1  Optional and can come only once
42 # O-n  Optional and can come more than once
43 # R-1  Required and can come only once
44 # R-n  Required and can come more than once
45
46 def uid2openobjectid(cr, uidval, oomodel, rdate):
47     """ UID To Open Object Id
48         @param cr: the current row, from the database cursor,
49         @param uidval: Get USerId vale
50         @oomodel: Open Object ModelName
51         @param rdate: Get Recurrent Date
52     """
53     __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
54     if not uidval:
55         return (False, None)
56     wematch = __rege.match(uidval.encode('utf8'))
57     if not wematch:
58         return (False, None)
59     else:
60         model, id, dbname = wematch.groups()
61         model_obj = pooler.get_pool(cr.dbname).get(model)
62         if (not model == oomodel) or (not dbname == cr.dbname):
63             return (False, None)
64         qry = 'select distinct(id) from %s' % model_obj._table
65         if rdate:
66             qry += " where recurrent_id='%s'" % (rdate)
67             cr.execute(qry)
68             r_id = cr.fetchone()
69             if r_id:
70                 return (id, r_id[0])
71         cr.execute(qry)
72         ids = map(lambda x: str(x[0]), cr.fetchall())
73         if id in ids:
74             return (id, None)
75         return (False, None)
76
77 def openobjectid2uid(cr, uidval, oomodel):
78     """ Gives the value of UID for VEVENT
79         @param cr: the current row, from the database cursor,
80         @param uidval: Id value of the Event
81         @oomodel: Open Object ModelName """
82
83     value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
84     return value
85
86 def mailto2str(arg):
87     """Take a dict of mail and convert to string.
88     """
89     ret = []
90     if isinstance(arg, dict):
91         args = [arg,]
92     else:
93         args = arg
94     
95     for ard in args:
96         rstr = ard.get('name','')
97         if ard.get('company',False):
98             rstr += ' (%s)' % ard.get('company')
99         if ard.get('email'):
100             rstr += ' <%s>' % ard.get('email')
101         ret.append(rstr)
102     return ', '.join(ret)
103
104 def str2mailto(emailstr, multi=False):
105     """Split one email string to a dict of name, company, mail parts
106
107        @param multi Return an array, recognize comma-sep
108     """
109     # TODO: move to tools or sth.
110     mege = re.compile(r'([^\(\<]+) *(\((.*?)\))? *(\< ?(.*?) ?\>)? ?(\((.*?)\))? *$')
111     
112     mailz= [emailstr,]
113     retz = []
114     if multi:
115         mailz = emailstr.split(',')
116     
117     for mas in mailz:
118         m = mege.match(mas.strip())
119         if not m:
120             # one of the rare non-matching strings is "sad" :(
121             # retz.append({ 'name': mas.strip() })
122             # continue
123             raise ValueError("Invalid email address %r" % mas)
124         rd = {  'name': m.group(1).strip(), 
125                 'email': m.group(5), }
126         if m.group(2):
127             rd['company'] = m.group(3).strip()
128         elif m.group(6):
129             rd['company'] = m.group(7).strip()
130         
131         if rd['name'].startswith('"') and rd['name'].endswith('"'):
132             rd['name'] = rd['name'][1:-1]
133         retz.append(rd)
134         
135     if multi:
136         return retz
137     else:
138         return retz[0]
139
140 def get_attribute_mapping(cr, uid, calname, context=None):
141     """ Attribute Mapping with Basic calendar fields and lines
142         @param cr: the current row, from the database cursor,
143         @param uid: the current user’s ID for security checks,
144         @param calname: Get Calendar name
145         @param context: A standard dictionary for contextual values """
146
147     if not context:
148         context = {}
149     pool = pooler.get_pool(cr.dbname)
150     field_obj = pool.get('basic.calendar.fields')
151     type_obj = pool.get('basic.calendar.lines')
152     domain = [('object_id.model', '=', context.get('model'))]
153     if context.get('calendar_id'):
154         domain.append(('calendar_id', '=', context.get('calendar_id')))
155     type_id = type_obj.search(cr, uid, domain)
156     fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])])
157     res = {}
158     for field in field_obj.browse(cr, uid, fids):
159         attr = field.name.name
160         res[attr] = {}
161         res[attr]['field'] = field.field_id.name
162         res[attr]['type'] = field.field_id.ttype
163         if field.fn == 'hours':
164             res[attr]['type'] = "timedelta"
165         if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
166             res[attr]['object'] = field.field_id.relation
167         elif res[attr]['type'] in ('selection') and field.mapping:
168             res[attr]['mapping'] = eval(field.mapping)
169     if not res.get('uid', None):
170         res['uid'] = {}
171         res['uid']['field'] = 'id'
172         res['uid']['type'] = "integer"
173     return res
174
175 def map_data(cr, uid, obj, context=None):
176     """ Map Data
177         @param self: The object pointer
178         @param cr: the current row, from the database cursor,"""
179
180     vals = {}
181     for map_dict in obj.__attribute__:
182         map_val = obj.ical_get(map_dict, 'value')
183         field = obj.ical_get(map_dict, 'field')
184         field_type = obj.ical_get(map_dict, 'type')
185         if field:
186             if field_type == 'selection':
187                 if not map_val:
188                     continue
189                 mapping = obj.__attribute__[map_dict].get('mapping', False)
190                 if mapping:
191                     map_val = mapping.get(map_val.lower(), False)
192                 else:
193                     map_val = map_val.lower()
194             if field_type == 'many2many':
195                 ids = []
196                 if not map_val:
197                     vals[field] = ids
198                     continue
199                 model = obj.__attribute__[map_dict].get('object', False)
200                 modobj = obj.pool.get(model)
201                 for map_vall in map_val:
202                     id = modobj.create(cr, uid, map_vall, context=context)
203                     ids.append(id)
204                 vals[field] = [(6, 0, ids)]
205                 continue
206             if field_type == 'many2one':
207                 id = None
208                 if not map_val or not isinstance(map_val, dict):
209                     vals[field] = id
210                     continue
211                 model = obj.__attribute__[map_dict].get('object', False)
212                 modobj = obj.pool.get(model)
213                 # check if the record exists or not
214                 key1 = map_val.keys()
215                 value1 = map_val.values()
216                 domain = [(key1[i], '=', value1[i]) for i in range(len(key1)) if value1[i]]
217                 exist_id = modobj.search(cr, uid, domain, context=context)
218                 if exist_id:
219                     id = exist_id[0]
220                 else:
221                     id = modobj.create(cr, uid, map_val, context=context)
222                 vals[field] = id
223                 continue
224             if field_type == 'timedelta':
225                 if map_val:
226                     vals[field] = (map_val.seconds/float(86400) + map_val.days)
227             vals[field] = map_val
228     return vals
229
230 class CalDAV(object):
231     __attribute__ = {}
232
233
234
235     def ical_set(self, name, value, type):
236         """ set calendar Attribute
237          @param self: The object pointer,
238          @param name: Get Attribute Name
239          @param value: Get Attribute Value
240          @param type: Get Attribute Type
241         """
242         if name in self.__attribute__ and self.__attribute__[name]:
243             self.__attribute__[name][type] = value
244         return True
245
246     def ical_get(self, name, type):
247         """ Get calendar Attribute
248          @param self: The object pointer,
249          @param name: Get Attribute Name
250          @param type: Get Attribute Type
251         """
252
253         if self.__attribute__.get(name):
254             val = self.__attribute__.get(name).get(type, None)
255             valtype =  self.__attribute__.get(name).get('type', None)
256             if type == 'value':
257                 if valtype and valtype == 'datetime' and val:
258                     if isinstance(val, list):
259                         val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
260                     else:
261                         val = val.strftime('%Y-%m-%d %H:%M:%S')
262             return  val
263         else:
264             return  self.__attribute__.get(name, None)
265
266     def ical_reset(self, type):
267         """ Reset Calendar Attribute
268          @param self: The object pointer,
269          @param type: Get Attribute Type
270         """
271
272         for name in self.__attribute__:
273             if self.__attribute__[name]:
274                 self.__attribute__[name][type] = None
275         return True
276
277     def format_date_tz(self, date, tz=None):
278         format = tools.DEFAULT_SERVER_DATETIME_FORMAT
279         return tools.server_to_local_timestamp(date, format, format, tz)
280
281     def parse_ics(self, cr, uid, child, cal_children=None, context=None):
282         """ parse calendaring and scheduling information
283         @param self: The object pointer
284         @param cr: the current row, from the database cursor,
285         @param uid: the current user’s ID for security checks,
286         @param context: A standard dictionary for contextual values """
287
288         att_data = []
289         exdates = []
290         _server_tzinfo = pytz.timezone(tools.get_server_timezone())
291         
292         for cal_data in child.getChildren():
293             if cal_data.name.lower() == 'organizer':
294                 dmail = { 'name': cal_data.params.get('CN', ['',])[0],
295                             'email': cal_data.value.replace('MAILTO:',''),
296                             # TODO: company? 
297                             }
298                 self.ical_set(cal_data.name.lower(), mailto2str(dmail), 'value')
299                 continue
300             if cal_data.name.lower() == 'attendee':
301                 ctx = context.copy()
302                 if cal_children:
303                     ctx.update({'model': cal_children[cal_data.name.lower()]})
304                 attendee = self.pool.get('basic.calendar.attendee')
305                 att_data.append(attendee.import_cal(cr, uid, cal_data, context=ctx))
306                 self.ical_set(cal_data.name.lower(), att_data, 'value')
307                 continue
308             if cal_data.name.lower() == 'valarm':
309                 alarm = self.pool.get('basic.calendar.alarm')
310                 ctx = context.copy()
311                 if cal_children:
312                     ctx.update({'model': cal_children[cal_data.name.lower()]})
313                 vals = alarm.import_cal(cr, uid, cal_data, context=ctx)
314                 self.ical_set(cal_data.name.lower(), vals, 'value')
315                 continue
316             if cal_data.name.lower() == 'exdate':
317                 exdates += cal_data.value
318                 exvals = []
319                 for exdate in exdates:
320                     exvals.append(datetime.fromtimestamp(time.mktime(exdate.utctimetuple())).strftime('%Y%m%dT%H%M%S'))
321                 self.ical_set(cal_data.name.lower(), ','.join(exvals), 'value')
322                 continue
323             if cal_data.name.lower() in self.__attribute__:
324                 if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
325                     self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
326                     date_utc = cal_data.value.astimezone(pytz.utc)
327                     self.ical_set(cal_data.name.lower(), date_utc, 'value')
328                     continue
329                 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
330         vals = map_data(cr, uid, self, context=context)
331         return vals
332
333     def create_ics(self, cr, uid, datas, name, ical, context=None):
334         """ create calendaring and scheduling information
335         @param self: The object pointer
336         @param cr: the current row, from the database cursor,
337         @param uid: the current user’s ID for security checks,
338         @param context: A standard dictionary for contextual values """
339
340         if not datas:
341             return
342         timezones = []
343         for data in datas:
344             tzval = None
345             exfield = None
346             exdates = []
347             vevent = ical.add(name)
348             for field in self.__attribute__.keys():
349                 map_field = self.ical_get(field, 'field')
350                 map_type = self.ical_get(field, 'type')
351                 if map_field in data.keys():
352                     if field == 'uid':
353                         model = context.get('model', None)
354                         if not model:
355                             continue
356                         uidval = openobjectid2uid(cr, data[map_field], model)
357                         model_obj = self.pool.get(model)
358                         r_ids = []
359                         if model_obj._columns.get('recurrent_uid', None):
360                             cr.execute('select id from %s  where recurrent_uid=%s'
361                                            % (model_obj._table, data[map_field]))
362                             r_ids = map(lambda x: x[0], cr.fetchall())
363                         if r_ids:
364                             rcal = self.export_cal(cr, uid, r_ids, 'vevent', context=context)
365                         if data.get('recurrent_uid', None):
366                             uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
367                         vevent.add('uid').value = uidval
368                     elif field == 'attendee' and data[map_field]:
369                         model = self.__attribute__[field].get('object', False)
370                         attendee_obj = self.pool.get('basic.calendar.attendee')
371                         vevent = attendee_obj.export_cal(cr, uid, model, \
372                                      data[map_field], vevent, context=context)
373                     elif field == 'valarm' and data[map_field]:
374                         model = self.__attribute__[field].get('object', False)
375                         ctx = context.copy()
376                         ctx.update({'model': model})
377                         alarm_obj = self.pool.get('basic.calendar.alarm')
378                         vevent = alarm_obj.export_cal(cr, uid, model, \
379                                     data[map_field][0], vevent, context=ctx)
380                     elif field == 'vtimezone' and data[map_field]:
381                         tzval = data[map_field]
382                         if tzval not in timezones:
383                             tz_obj = self.pool.get('basic.calendar.timezone')
384                             ical = tz_obj.export_cal(cr, uid, None, \
385                                          data[map_field], ical, context=context)
386                             timezones.append(data[map_field])
387                         if exfield:
388                             exfield.params['TZID'] = [tzval.title()]
389                             exdates_updated = []
390                             for exdate in exdates:
391                                 date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
392                                 dest_date = self.format_date_tz(date1, tzval.title())
393                                 ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
394                                 exdates_updated.append(ex_date)
395                             exfield.value = map(parser.parse, exdates_updated)
396                     elif field == 'organizer' and data[map_field]:
397                         organizer = str2mailto(data[map_field])
398                         event_org = vevent.add('organizer')
399                         event_org.params['CN'] = [organizer['name']]
400                         event_org.value = 'MAILTO:' + (organizer.get('email') or '')
401                         # TODO: company?
402                     elif data[map_field]:
403                         if map_type in ("char", "text"):
404                             if field in ('exdate'):
405                                 exfield = vevent.add(field)
406                                 exdates = (data[map_field]).split(',')
407                                 if tzval:
408                                     exfield.params['TZID'] = [tzval.title()]
409                                     exdates_updated = []
410                                     for exdate in exdates:
411                                         date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
412                                         dest_date = self.format_date_tz(date1, tzval.title())
413                                         ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
414                                         exdates_updated.append(ex_date)
415                                     exdates = exdates_updated
416                                 exfield.value = map(parser.parse, exdates)
417                             else:
418                                 vevent.add(field).value = tools.ustr(data[map_field])
419                         elif map_type in ('datetime', 'date') and data[map_field]:
420                             dtfield = vevent.add(field)
421                             if tzval:
422                                 dest_date = self.format_date_tz(data[map_field], tzval.title())
423                                 dtfield.params['TZID'] = [tzval.title()]
424                                 dtfield.value = parser.parse(dest_date)
425                             else:
426                                 dtfield.value = parser.parse(data[map_field])
427                         elif map_type == "timedelta":
428                             vevent.add(field).value = timedelta(hours=data[map_field])
429                         elif map_type == "many2one":
430                             vevent.add(field).value = tools.ustr(data.get(map_field)[1])
431                         elif map_type in ("float", "integer"):
432                             vevent.add(field).value = str(data.get(map_field))
433                         elif map_type == "selection":
434                             if not self.ical_get(field, 'mapping'):
435                                 vevent.add(field).value = (tools.ustr(data[map_field])).upper()
436                             else:
437                                 for key1, val1 in self.ical_get(field, 'mapping').items():
438                                     if val1 == data[map_field]:
439                                         vevent.add(field).value = key1
440         return vevent
441
442     def check_import(self, cr, uid, vals, context=None):
443         """
444             @param self: The object pointer
445             @param cr: the current row, from the database cursor,
446             @param uid: the current user’s ID for security checks,
447             @param vals: Get Values
448             @param context: A standard dictionary for contextual values
449         """
450         if not context:
451             context = {}
452         ids = []
453         model_obj = self.pool.get(context.get('model'))
454         recur_pool = {}
455         try:
456             for val in vals:
457                 # Compute value of duration
458                 if 'date_deadline' in val and 'duration' not in val:
459                     start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
460                     end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
461                     diff = end - start
462                     val['duration'] = (diff.seconds/float(86400) + diff.days) * 24
463                 exists, r_id = calendar.uid2openobjectid(cr, val['id'], context.get('model'), \
464                                                                  val.get('recurrent_id'))
465                 if val.has_key('create_date'):
466                     val.pop('create_date')
467                 u_id = val.get('id', None)
468                 val.pop('id')
469                 if exists and r_id:
470                     val.update({'recurrent_uid': exists})
471                     model_obj.write(cr, uid, [r_id], val)
472                     ids.append(r_id)
473                 elif exists:
474                     model_obj.write(cr, uid, [exists], val)
475                     ids.append(exists)
476                 else:
477                     if u_id in recur_pool and val.get('recurrent_id'):
478                         val.update({'recurrent_uid': recur_pool[u_id]})
479                         revent_id = model_obj.create(cr, uid, val)
480                         ids.append(revent_id)
481                     else:
482                         __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
483                         wematch = __rege.match(u_id.encode('utf8'))
484                         if wematch:
485                             model, recur_id, dbname = wematch.groups()
486                             val.update({'recurrent_uid': recur_id})
487                         event_id = model_obj.create(cr, uid, val)
488                         recur_pool[u_id] = event_id
489                         ids.append(event_id)
490         except Exception:
491             raise
492         return ids
493
494     def export_cal(self, cr, uid, datas, vobj=None, context=None):
495         """ Export Calendar
496             @param self: The object pointer
497             @param cr: the current row, from the database cursor,
498             @param uid: the current user’s ID for security checks,
499             @param datas: Get Data's for caldav
500             @param context: A standard dictionary for contextual values
501         """
502
503         try:
504             self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
505             ical = vobject.iCalendar()
506             self.create_ics(cr, uid, datas, vobj, ical, context=context)
507             return ical
508         except Exception, e:
509             raise  # osv.except_osv(('Error !'), (str(e)))
510
511     def import_cal(self, cr, uid, content, data_id=None, context=None):
512         """ Import Calendar
513             @param self: The object pointer
514             @param cr: the current row, from the database cursor,
515             @param uid: the current user’s ID for security checks,
516             @param data_id: Get Data’s ID or False
517             @param context: A standard dictionary for contextual values
518         """
519
520         ical_data = content
521         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
522         parsedCal = vobject.readOne(ical_data)
523         res = []
524         vals = {}
525         for child in parsedCal.getChildren():
526             if child.name.lower() in ('vevent', 'vtodo'):
527                 vals = self.parse_ics(cr, uid, child, context=context)
528             else:
529                 vals = {}
530                 continue
531             if vals: res.append(vals)
532             self.ical_reset('value')
533         return res
534
535 class Calendar(CalDAV, osv.osv):
536     _name = 'basic.calendar'
537     _calname = 'calendar'
538
539     __attribute__ = {
540         'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
541         'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
542                            #             or the minimum and maximum range of the iCalendar specification
543                            #             that is required in order to interpret the iCalendar object.
544         'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
545         'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
546         'vevent': None, # Use: O-n, Type: Collection of Event class
547         'vtodo': None, # Use: O-n, Type: Collection of ToDo class
548         'vjournal': None, # Use: O-n, Type: Collection of Journal class
549         'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
550         'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
551     }
552     _columns = {
553             'name': fields.char("Name", size=64),
554             'user_id': fields.many2one('res.users', 'Owner'),
555             'collection_id': fields.many2one('document.directory', 'Collection', \
556                                            required=True),
557             'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
558                                     string="Type", size=64),
559             'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
560             'create_date': fields.datetime('Created Date', readonly=True),
561             'write_date': fields.datetime('Modifided Date', readonly=True),
562             'description': fields.text("description"),
563     }
564
565     def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
566         if not context:
567             context = {}
568         if not domain:
569             domain = []
570         res = []
571         ctx_res_id = context.get('res_id', None)
572         ctx_model = context.get('model', None)
573         for cal in self.browse(cr, uid, ids):
574             for line in cal.line_ids:
575                 if ctx_model and ctx_model != line.object_id.model:
576                     continue
577                 if line.name in ('valarm', 'attendee'):
578                     continue
579                 line_domain = eval(line.domain or '[]')
580                 line_domain += domain
581                 if ctx_res_id:
582                     line_domain += [('id','=',ctx_res_id)]
583                 mod_obj = self.pool.get(line.object_id.model)
584                 data_ids = mod_obj.search(cr, uid, line_domain, context=context)
585                 for data in mod_obj.browse(cr, uid, data_ids, context):
586                     ctx = parent and parent.context or None
587                     node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
588                     res.append(node)
589         return res
590
591     def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
592         """ Export Calendar
593             @param ids: List of calendar’s IDs
594             @param vobj: the type of object to export
595             
596             @return the ical data.
597         """
598         if not context:
599            context = {}
600         ctx_model = context.get('model', None)
601         ctx_res_id = context.get('res_id', None)
602         ical = vobject.iCalendar()
603         for cal in self.browse(cr, uid, ids):
604             for line in cal.line_ids:
605                 if ctx_model and ctx_model != line.object_id.model:
606                     continue
607                 if line.name in ('valarm', 'attendee'):
608                     continue
609                 domain = eval(line.domain or '[]')
610                 if ctx_res_id:
611                     domain += [('id','=',ctx_res_id)]
612                 mod_obj = self.pool.get(line.object_id.model)
613                 data_ids = mod_obj.search(cr, uid, domain, context=context)
614                 datas = mod_obj.read(cr, uid, data_ids, context=context)
615                 context.update({'model': line.object_id.model,
616                                         'calendar_id': cal.id
617                                         })
618                 self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
619                 self.create_ics(cr, uid, datas, line.name, ical, context=context)
620         return ical.serialize()
621
622     def import_cal(self, cr, uid, content, data_id=None, context=None):
623         """ Import Calendar
624             @param self: The object pointer
625             @param cr: the current row, from the database cursor,
626             @param uid: the current user’s ID for security checks,
627             @param data_id: Get Data’s ID or False
628             @param context: A standard dictionary for contextual values
629         """
630
631         if not context:
632             context = {}
633         vals = []
634         ical_data = content
635         parsedCal = vobject.readOne(ical_data)
636         if not data_id:
637             data_id = self.search(cr, uid, [])[0]
638         cal = self.browse(cr, uid, data_id, context=context)
639         cal_children = {}
640         count = 0
641         for line in cal.line_ids:
642             cal_children[line.name] = line.object_id.model
643         objs = []
644         checked = True
645         for child in parsedCal.getChildren():
646             if child.name.lower() in cal_children:
647                 context.update({'model': cal_children[child.name.lower()],
648                                 'calendar_id': cal['id']
649                                 })
650                 self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
651                 val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
652                 vals.append(val)
653                 objs.append(cal_children[child.name.lower()])
654         
655         res = []
656         for obj_name in list(set(objs)):
657             obj = self.pool.get(obj_name)
658             if hasattr(obj, 'check_import'):
659                 r = obj.check_import(cr, uid, vals, context=context)
660                 checked = True
661                 res.extend(r)
662
663         if not checked:
664             r = self.check_import(cr, uid, vals, context=context)
665             res.extend(r)
666         return res
667 Calendar()
668
669
670 class basic_calendar_line(osv.osv):
671     """ Calendar Lines """
672
673     _name = 'basic.calendar.lines'
674     _description = 'Calendar Lines'
675
676     _columns = {
677             'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
678                                     ('valarm', 'Alarm'), \
679                                     ('attendee', 'Attendee')], \
680                                     string="Type", size=64),
681             'object_id': fields.many2one('ir.model', 'Object'),
682             'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
683                                        required=True, ondelete='cascade'),
684             'domain': fields.char('Domain', size=124),
685             'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
686     }
687
688     _defaults = {
689         'domain': lambda *a: '[]',
690     }
691
692     def create(self, cr, uid, vals, context=None):
693         """ create calendar's line
694             @param self: The object pointer
695             @param cr: the current row, from the database cursor,
696             @param uid: the current user’s ID for security checks,
697             @param vals: Get the Values
698             @param context: A standard dictionary for contextual values
699         """
700
701         cr.execute("Select count(id) from basic_calendar_lines \
702                                 where name='%s' and calendar_id=%s" % (vals.get('name'), vals.get('calendar_id')))
703         res = cr.fetchone()
704         if res:
705             if res[0] > 0:
706                 raise osv.except_osv(_('Warning !'), _('Can not create \
707 line "%s" more than once' % (vals.get('name'))))
708         return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
709
710 basic_calendar_line()
711
712
713 class basic_calendar_attribute(osv.osv):
714     _name = 'basic.calendar.attributes'
715     _description = 'Calendar attributes'
716     _columns = {
717         'name': fields.char("Name", size=64, required=True),
718         'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
719                                     ('alarm', 'Alarm'), \
720                                     ('attendee', 'Attendee')], \
721                                     string="Type", size=64, required=True),
722     }
723
724 basic_calendar_attribute()
725
726
727 class basic_calendar_fields(osv.osv):
728     """ Calendar fields """
729
730     _name = 'basic.calendar.fields'
731     _description = 'Calendar fields'
732
733     _columns = {
734         'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
735         'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
736         'type_id': fields.many2one('basic.calendar.lines', 'Type', \
737                                    required=True, ondelete='cascade'),
738         'expr': fields.char("Expression", size=64),
739         'fn': fields.selection([('field', 'Use the field'),
740                         ('const', 'Expression as constant'),
741                         ('hours', 'Interval in hours'),
742                         ], 'Function'),
743         'mapping': fields.text('Mapping'),
744     }
745
746     _defaults = {
747         'fn': lambda *a: 'field',
748     }
749
750     _sql_constraints = [
751         ( 'name_type_uniq', 'UNIQUE(name, type_id)', 'Can not map a field more than once'),
752     ]
753
754     def check_line(self, cr, uid, vals, name, context=None):
755         """ check calendar's line
756             @param self: The object pointer
757             @param cr: the current row, from the database cursor,
758             @param uid: the current user’s ID for security checks,
759             @param vals: Get Values
760             @param context: A standard dictionary for contextual values
761         """
762         f_obj = self.pool.get('ir.model.fields')
763         field = f_obj.browse(cr, uid, vals['field_id'], context=context)
764         relation = field.relation
765         line_obj = self.pool.get('basic.calendar.lines')
766         l_id = line_obj.search(cr, uid, [('name', '=', name)])
767         if l_id:
768             line = line_obj.browse(cr, uid, l_id, context=context)[0]
769             line_rel = line.object_id.model
770             if (relation != 'NULL') and (not relation == line_rel):
771                 raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines' % (name)))
772         return True
773
774     def create(self, cr, uid, vals, context=None):
775         """ Create Calendar's fields
776             @param self: The object pointer
777             @param cr: the current row, from the database cursor,
778             @param uid: the current user’s ID for security checks,
779             @param vals: Get Values
780             @param context: A standard dictionary for contextual values
781         """
782
783         cr.execute('select name from basic_calendar_attributes \
784                             where id=%s' % (vals.get('name')))
785         name = cr.fetchone()
786         name = name[0]
787         if name in ('valarm', 'attendee'):
788             self.check_line(cr, uid, vals, name, context=context)
789         return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
790
791     def write(self, cr, uid, ids, vals, context=None):
792         """ write Calendar's fields
793             @param self: The object pointer
794             @param cr: the current row, from the database cursor,
795             @param uid: the current user’s ID for security checks,
796             @param vals: Get Values
797             @param context: A standard dictionary for contextual values
798         """
799
800         if not vals:
801             return
802         for id in ids:
803             field = self.browse(cr, uid, id, context=context)
804             name = field.name.name
805             if name in ('valarm', 'attendee'):
806                 self.check_line(cr, uid, vals, name, context=context)
807         return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
808
809 basic_calendar_fields()
810
811
812 class Event(CalDAV, osv.osv_memory):
813     _name = 'basic.calendar.event'
814     _calname = 'vevent'
815     __attribute__ = {
816         'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar  component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
817         '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.
818         'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
819         'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
820         'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
821         '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.
822         'location': None, # Use: O-1, Type: TEXT            Defines the intended venue for the activity defined by a calendar component.
823         'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
824         'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
825         'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
826         'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
827         'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
828         'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
829         'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
830         'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
831         'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
832         'recurid': None,
833         'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
834         'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
835         'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
836         'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
837         'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a  reference to contact information associated with the calendar component.
838         'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
839         'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
840         'rstatus': None,
841         'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
842                                 #  like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
843         '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
844         'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
845         'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
846         'x-prop': None,
847         'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
848         'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
849     }
850
851     def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
852         """ Export calendar
853             @param self: The object pointer
854             @param cr: the current row, from the database cursor,
855             @param uid: the current user’s ID for security checks,
856             @param datas: Get datas
857             @param context: A standard dictionary for contextual values
858         """
859
860         return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
861
862 Event()
863
864
865 class ToDo(CalDAV, osv.osv_memory):
866     _name = 'basic.calendar.todo'
867     _calname = 'vtodo'
868
869     __attribute__ = {
870                 'class': None,
871                 'completed': None,
872                 'created': None,
873                 'description': None,
874                 'dtstamp': None,
875                 'dtstart': None,
876                 'duration': None,
877                 'due': None,
878                 'geo': None,
879                 'last-mod ': None,
880                 'location': None,
881                 'organizer': None,
882                 'percent': None,
883                 'priority': None,
884                 'recurid': None,
885                 'seq': None,
886                 'status': None,
887                 'summary': None,
888                 'uid': None,
889                 'url': None,
890                 'attach': None,
891                 'attendee': None,
892                 'categories': None,
893                 'comment': None,
894                 'contact': None,
895                 'exdate': None,
896                 'exrule': None,
897                 'rstatus': None,
898                 'related': None,
899                 'resources': None,
900                 'rdate': None,
901                 'rrule': None,
902             }
903
904     def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
905         """ Export Calendar
906             @param self: The object pointer
907             @param cr: the current row, from the database cursor,
908             @param uid: the current user’s ID for security checks,
909             @param datas: Get datas
910             @param context: A standard dictionary for contextual values
911         """
912
913         return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
914
915 ToDo()
916
917
918 class Journal(CalDAV):
919     __attribute__ = {
920     }
921
922
923 class FreeBusy(CalDAV):
924     __attribute__ = {
925     'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a  reference to contact information associated with the calendar component.
926     'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
927     'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
928     'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
929     'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
930     'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
931     'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
932     'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
933     'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
934     'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
935     'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
936     'rstatus': None,
937     'X-prop': None,
938     }
939
940
941 class Timezone(CalDAV, osv.osv_memory):
942     _name = 'basic.calendar.timezone'
943     _calname = 'vtimezone'
944
945     __attribute__ = {
946     'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
947     '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.
948     '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.
949     'standardc': {'tzprop': None}, # Use: R-1,
950     'daylightc': {'tzprop': None}, # Use: R-1,
951     'x-prop': None, # Use: O-n, Type: Text,
952     }
953
954     def get_name_offset(self, cr, uid, tzid, context=None):
955         """ Get Name Offset value
956             @param self: The object pointer
957             @param cr: the current row, from the database cursor,
958             @param uid: the current user’s ID for security checks,
959             @param context: A standard dictionary for contextual values
960         """
961
962         mytz = pytz.timezone(tzid.title())
963         mydt = datetime.now(tz=mytz)
964         offset = mydt.utcoffset()
965         val = offset.days * 24 + float(offset.seconds) / 3600
966         realoffset = '%02d%02d' % (math.floor(abs(val)), \
967                                  round(abs(val) % 1 + 0.01, 2) * 60)
968         realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
969         return (mydt.tzname(), realoffset)
970
971     def export_cal(self, cr, uid, model, tzid, ical, context=None):
972         """ Export Calendar
973             @param self: The object pointer
974             @param cr: the current row, from the database cursor,
975             @param uid: the current user’s ID for security checks,
976             @param model: Get Model's name
977             @param context: A standard dictionary for contextual values
978         """
979         if not context:
980             context = {}
981         ctx = context.copy()
982         ctx.update({'model': model})
983         cal_tz = ical.add('vtimezone')
984         cal_tz.add('TZID').value = tzid.title()
985         tz_std = cal_tz.add('STANDARD')
986         tzname, offset = self.get_name_offset(cr, uid, tzid)
987         tz_std.add("TZOFFSETFROM").value = offset
988         tz_std.add("TZOFFSETTO").value = offset
989         #TODO: Get start date for timezone
990         tz_std.add("DTSTART").value = datetime.strptime('1970-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')
991         tz_std.add("TZNAME").value = tzname
992         return ical
993
994     def import_cal(self, cr, uid, ical_data, context=None):
995         """ Import Calendar
996             @param self: The object pointer
997             @param cr: the current row, from the database cursor,
998             @param uid: the current user’s ID for security checks,
999             @param ical_data: Get calendar's data
1000             @param context: A standard dictionary for contextual values
1001         """
1002
1003         for child in ical_data.getChildren():
1004             if child.name.lower() == 'tzid':
1005                 tzname = child.value
1006                 self.ical_set(child.name.lower(), tzname, 'value')
1007         vals = map_data(cr, uid, self, context=context)
1008         return vals
1009
1010 Timezone()
1011
1012
1013 class Alarm(CalDAV, osv.osv_memory):
1014     _name = 'basic.calendar.alarm'
1015     _calname = 'alarm'
1016
1017     __attribute__ = {
1018     'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
1019     '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
1020     'summary': None, # Use: R-1, Type: Text        Which contains the text to be used as the message subject. Use for EMAIL
1021     '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
1022     '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
1023     '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
1024     '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
1025     '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.
1026     'x-prop': None,
1027     }
1028
1029     def export_cal(self, cr, uid, model, alarm_id, vevent, context=None):
1030         """ Export Calendar
1031             @param self: The object pointer
1032             @param cr: the current row, from the database cursor,
1033             @param uid: the current user’s ID for security checks,
1034             @param model: Get Model's name
1035             @param alarm_id: Get Alarm's Id
1036             @param context: A standard dictionary for contextual values
1037         """
1038         if not context:
1039             context = {}
1040         valarm = vevent.add('valarm')
1041         alarm_object = self.pool.get(model)
1042         alarm_data = alarm_object.read(cr, uid, alarm_id, [])
1043
1044         # Compute trigger data
1045         interval = alarm_data['trigger_interval']
1046         occurs = alarm_data['trigger_occurs']
1047         duration = (occurs == 'after' and alarm_data['trigger_duration']) \
1048                                         or -(alarm_data['trigger_duration'])
1049         related = alarm_data['trigger_related']
1050         trigger = valarm.add('TRIGGER')
1051         trigger.params['related'] = [related.upper()]
1052         if interval == 'days':
1053             delta = timedelta(days=duration)
1054         if interval == 'hours':
1055             delta = timedelta(hours=duration)
1056         if interval == 'minutes':
1057             delta = timedelta(minutes=duration)
1058         trigger.value = delta
1059
1060         # Compute other details
1061         valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
1062         valarm.add('ACTION').value = alarm_data['action']
1063         return vevent
1064
1065     def import_cal(self, cr, uid, ical_data, context=None):
1066         """ Import Calendar
1067             @param self: The object pointer
1068             @param cr: the current row, from the database cursor,
1069             @param uid: the current user’s ID for security checks,
1070             @param ical_data: Get calendar's Data
1071             @param context: A standard dictionary for contextual values
1072         """
1073
1074         ctx = context.copy()
1075         ctx.update({'model': context.get('model', None)})
1076         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1077         for child in ical_data.getChildren():
1078             if child.name.lower() == 'trigger':
1079                 seconds = child.value.seconds
1080                 days = child.value.days
1081                 diff = (days * 86400) +  seconds
1082                 interval = 'days'
1083                 related = 'before'
1084                 if not seconds:
1085                     duration = abs(days)
1086                     related = days > 0 and 'after' or 'before'
1087                 elif (abs(diff) / 3600) == 0:
1088                     duration = abs(diff / 60)
1089                     interval = 'minutes'
1090                     related = days >= 0 and 'after' or 'before'
1091                 else:
1092                     duration = abs(diff / 3600)
1093                     interval = 'hours'
1094                     related = days >= 0 and 'after' or 'before'
1095                 self.ical_set('trigger_interval', interval, 'value')
1096                 self.ical_set('trigger_duration', duration, 'value')
1097                 self.ical_set('trigger_occurs', related.lower(), 'value')
1098                 if child.params:
1099                     if child.params.get('related'):
1100                         self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
1101             else:
1102                 self.ical_set(child.name.lower(), child.value.lower(), 'value')
1103         vals = map_data(cr, uid, self, context=context)
1104         return vals
1105
1106 Alarm()
1107
1108
1109 class Attendee(CalDAV, osv.osv_memory):
1110     _name = 'basic.calendar.attendee'
1111     _calname = 'attendee'
1112
1113     __attribute__ = {
1114     'cutype': None, # Use: 0-1    Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
1115     'member': None, # Use: 0-1    Specify the group or list membership of the calendar user specified by the property.
1116     '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"
1117     '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".
1118     '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.
1119     'delegated-to': None, # Use: 0-1    Specify the calendar users to whom the calendar user specified by the property has delegated participation.
1120     'delegated-from': None, # Use: 0-1    Specify the calendar users that have delegated their participation to the calendar user specified by the property.
1121     'sent-by': None, # Use: 0-1    Specify the calendar user that is acting on behalf of the calendar user specified by the property.
1122     'cn': None, # Use: 0-1    Specify the common name to be associated with the calendar user specified by the property.
1123     'dir': None, # Use: 0-1    Specify reference to a directory entry associated with the calendar user specified by the property.
1124     'language': None, # Use: 0-1    Specify the language for text values in a property or property parameter.
1125     }
1126
1127     def import_cal(self, cr, uid, ical_data, context=None):
1128         """ Import Calendar
1129             @param self: The object pointer
1130             @param cr: the current row, from the database cursor,
1131             @param uid: the current user’s ID for security checks,
1132             @param ical_data: Get calendar's Data
1133             @param context: A standard dictionary for contextual values
1134         """
1135
1136         ctx = context.copy()
1137         ctx.update({'model': context.get('model', None)})
1138         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1139         for para in ical_data.params:
1140             if para.lower() == 'cn':
1141                 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
1142                         ical_data.value, 'value')
1143             else:
1144                 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
1145         if not ical_data.params.get('CN'):
1146             self.ical_set('cn', ical_data.value, 'value')
1147         vals = map_data(cr, uid, self, context=context)
1148         return vals
1149
1150     def export_cal(self, cr, uid, model, attendee_ids, vevent, context=None):
1151         """ Export Calendar
1152             @param self: The object pointer
1153             @param cr: the current row, from the database cursor,
1154             @param uid: the current user’s ID for security checks,
1155             @param model: Get model's name
1156             @param attendee_ids: Get Attendee's Id
1157             @param context: A standard dictionary for contextual values
1158         """
1159         if not context:
1160             context = {}
1161         attendee_object = self.pool.get(model)
1162         ctx = context.copy()
1163         ctx.update({'model': model})
1164         self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1165         for attendee in attendee_object.read(cr, uid, attendee_ids, []):
1166             attendee_add = vevent.add('attendee')
1167             cn_val = ''
1168             for a_key, a_val in self.__attribute__.items():
1169                 if attendee[a_val['field']] and a_val['field'] != 'cn':
1170                     if a_val['type'] in ('text', 'char', 'selection'):
1171                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1172                     elif a_val['type'] == 'boolean':
1173                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1174                 if a_val['field'] == 'cn' and attendee[a_val['field']]:
1175                     cn_val = [str(attendee[a_val['field']])]
1176                     if cn_val:
1177                         attendee_add.params['CN'] = cn_val
1178             if not attendee['email']:
1179                 attendee_add.value = 'MAILTO:'
1180                 #raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
1181             elif attendee['email']:
1182                 attendee_add.value = 'MAILTO:' + attendee['email']
1183         return vevent
1184
1185 Attendee()
1186
1187 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: