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