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