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