Launchpad automatic translations update.
[odoo/odoo.git] / addons / caldav / calendar.py
index 503a027..a70a31b 100644 (file)
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
 #    GNU Affero General Public License for more details.
 #
 #    You should have received a copy of the GNU Affero General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
 
 from datetime import datetime, timedelta
 from dateutil import parser
 from dateutil.rrule import *
-from osv import osv
+from osv import osv, fields
+from tools.translate import _
+import math
+import pooler
+import pytz
 import re
-import vobject
-import common
+import tools
+import time
+import logging
+from caldav_node import res_node_calendar
+from orm_utils import get_last_modified
+from tools.safe_eval import safe_eval as eval
+
+try:
+    import vobject
+except ImportError:
+    raise osv.except_osv(_('vobject Import Error!'), _('Please install python-vobject from http://vobject.skyhouseconsulting.com/'))
 
 # O-1  Optional and can come only once
 # O-n  Optional and can come more than once
 # R-1  Required and can come only once
 # R-n  Required and can come more than once
 
-def map_data(cr, uid, obj):
+def uid2openobjectid(cr, uidval, oomodel, rdate):
+    """ UID To Open Object Id
+        @param cr: the current row, from the database cursor,
+        @param uidval: Get USerId vale
+        @oomodel: Open Object ModelName
+        @param rdate: Get Recurrent Date
+    """
+    __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
+    if not uidval:
+        return (False, None)
+    wematch = __rege.match(uidval.encode('utf8'))
+    if not wematch:
+        return (False, None)
+    else:
+        model, id, dbname = wematch.groups()
+        model_obj = pooler.get_pool(cr.dbname).get(model)
+        if (not model == oomodel) or (not dbname == cr.dbname):
+            return (False, None)
+        qry = 'SELECT DISTINCT(id) FROM %s' % model_obj._table
+        if rdate:
+            qry += " WHERE recurrent_id=%s"
+            cr.execute(qry, (rdate,))
+            r_id = cr.fetchone()
+            if r_id:
+                return (id, r_id[0])
+            else:
+                return (False, None)
+        cr.execute(qry)
+        ids = map(lambda x: str(x[0]), cr.fetchall())
+        if id in ids:
+            return (id, None)
+        return (False, None)
+
+def openobjectid2uid(cr, uidval, oomodel):
+    """ Gives the value of UID for VEVENT
+        @param cr: the current row, from the database cursor,
+        @param uidval: Id value of the Event
+        @oomodel: Open Object ModelName """
+
+    value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
+    return value
+
+def mailto2str(arg):
+    """Take a dict of mail and convert to string.
+    """
+    ret = []
+    if isinstance(arg, dict):
+        args = [arg,]
+    else:
+        args = arg
+
+    for ard in args:
+        rstr = ard.get('name','')
+        if ard.get('company',False):
+            rstr += ' (%s)' % ard.get('company')
+        if ard.get('email'):
+            rstr += ' <%s>' % ard.get('email')
+        ret.append(rstr)
+    return ', '.join(ret)
+
+def str2mailto(emailstr, multi=False):
+    """Split one email string to a dict of name, company, mail parts
+
+       @param multi Return an array, recognize comma-sep
+    """
+    # TODO: move to tools or sth.
+    mege = re.compile(r'([^\(\<]+) *(\((.*?)\))? *(\< ?(.*?) ?\>)? ?(\((.*?)\))? *$')
+
+    mailz= [emailstr,]
+    retz = []
+    if multi:
+        mailz = emailstr.split(',')
+
+    for mas in mailz:
+        m = mege.match(mas.strip())
+        if not m:
+            # one of the rare non-matching strings is "sad" :(
+            # retz.append({ 'name': mas.strip() })
+            # continue
+            raise ValueError("Invalid email address %r" % mas)
+        rd = {  'name': m.group(1).strip(),
+                'email': m.group(5), }
+        if m.group(2):
+            rd['company'] = m.group(3).strip()
+        elif m.group(6):
+            rd['company'] = m.group(7).strip()
+
+        if rd['name'].startswith('"') and rd['name'].endswith('"'):
+            rd['name'] = rd['name'][1:-1]
+        retz.append(rd)
+
+    if multi:
+        return retz
+    else:
+        return retz[0]
+
+def get_attribute_mapping(cr, uid, calname, context=None):
+    """ Attribute Mapping with Basic calendar fields and lines
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param calname: Get Calendar name
+        @param context: A standard dictionary for contextual values """
+
+    if context is None:
+        context = {}
+    pool = pooler.get_pool(cr.dbname)
+    field_obj = pool.get('basic.calendar.fields')
+    type_obj = pool.get('basic.calendar.lines')
+    domain = [('object_id.model', '=', context.get('model'))]
+    if context.get('calendar_id'):
+        domain.append(('calendar_id', '=', context.get('calendar_id')))
+    type_id = type_obj.search(cr, uid, domain)
+    fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])])
+    res = {}
+    for field in field_obj.browse(cr, uid, fids, context=context):
+        attr = field.name.name
+        res[attr] = {}
+        res[attr]['field'] = field.field_id.name
+        res[attr]['type'] = field.field_id.ttype
+        if field.fn == 'datetime_utc':
+            res[attr]['type'] = 'utc'
+        if field.fn == 'hours':
+            res[attr]['type'] = "timedelta"
+        if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
+            res[attr]['object'] = field.field_id.relation
+        elif res[attr]['type'] in ('selection') and field.mapping:
+            res[attr]['mapping'] = eval(field.mapping)
+    if not res.get('uid', None):
+        res['uid'] = {}
+        res['uid']['field'] = 'id'
+        res['uid']['type'] = "integer"
+    return res
+
+def map_data(cr, uid, obj, context=None):
+    """ Map Data
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,"""
+
     vals = {}
     for map_dict in obj.__attribute__:
         map_val = obj.ical_get(map_dict, 'value')
@@ -42,9 +192,11 @@ def map_data(cr, uid, obj):
             if field_type == 'selection':
                 if not map_val:
                     continue
-                mapping =obj.__attribute__[map_dict].get('mapping', False)
+                if type(map_val) == list and len(map_val): #TOFIX: why need to check this
+                    map_val = map_val[0]
+                mapping = obj.__attribute__[map_dict].get('mapping', False)
                 if mapping:
-                    map_val = mapping[map_val.lower()]
+                    map_val = mapping.get(map_val.lower(), False)
                 else:
                     map_val = map_val.lower()
             if field_type == 'many2many':
@@ -55,121 +207,360 @@ def map_data(cr, uid, obj):
                 model = obj.__attribute__[map_dict].get('object', False)
                 modobj = obj.pool.get(model)
                 for map_vall in map_val:
-                    id = modobj.create(cr, uid, map_vall)
+                    id = modobj.create(cr, uid, map_vall, context=context)
                     ids.append(id)
                 vals[field] = [(6, 0, ids)]
                 continue
             if field_type == 'many2one':
                 id = None
-                if not map_val:
+                if not map_val or not isinstance(map_val, dict):
                     vals[field] = id
                     continue
                 model = obj.__attribute__[map_dict].get('object', False)
                 modobj = obj.pool.get(model)
-                id = modobj.create(cr, uid, map_val)
+                # check if the record exists or not
+                key1 = map_val.keys()
+                value1 = map_val.values()
+                domain = [(key1[i], '=', value1[i]) for i in range(len(key1)) if value1[i]]
+                exist_id = modobj.search(cr, uid, domain, context=context)
+                if exist_id:
+                    id = exist_id[0]
+                else:
+                    id = modobj.create(cr, uid, map_val, context=context)
                 vals[field] = id
                 continue
-            if map_val:
-                vals[field] = map_val
+            if field_type == 'timedelta':
+                if map_val:
+                    vals[field] = (map_val.seconds/float(86400) + map_val.days)
+            vals[field] = map_val
     return vals
 
 class CalDAV(object):
-    __attribute__ = {
-    }
-    def get_recurrent_dates(self, rrulestring, exdate, startdate=None):
-        if not startdate:
-            startdate = datetime.now()         
-        rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
-    
-        for date in exdate:
-            datetime_obj = todate(date)
-            rset1._exdate.append(datetime_obj)
-        re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
-        return re_dates
+    __attribute__ = {}
+    _logger = logging.getLogger('document.caldav')
 
     def ical_set(self, name, value, type):
+        """ set calendar Attribute
+         @param self: The object pointer,
+         @param name: Get Attribute Name
+         @param value: Get Attribute Value
+         @param type: Get Attribute Type
+        """
         if name in self.__attribute__ and self.__attribute__[name]:
             self.__attribute__[name][type] = value
         return True
 
     def ical_get(self, name, type):
+        """ Get calendar Attribute
+         @param self: The object pointer,
+         @param name: Get Attribute Name
+         @param type: Get Attribute Type
+        """
         if self.__attribute__.get(name):
             val = self.__attribute__.get(name).get(type, None)
             valtype =  self.__attribute__.get(name).get('type', None)
             if type == 'value':
-                if valtype and valtype=='datetime' and val:
+                if valtype and valtype == 'datetime' and val:
                     if isinstance(val, list):
                         val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
                     else:
                         val = val.strftime('%Y-%m-%d %H:%M:%S')
-                if valtype and valtype=='integer' and val:
-                    val = int(val)
             return  val
         else:
             return  self.__attribute__.get(name, None)
 
     def ical_reset(self, type):
+        """ Reset Calendar Attribute
+         @param self: The object pointer,
+         @param type: Get Attribute Type
+        """
         for name in self.__attribute__:
             if self.__attribute__[name]:
                 self.__attribute__[name][type] = None
         return True
 
-    def export_ical(self, cr, uid, datas, vobj=None, context={}):
-        ical = vobject.iCalendar()
+    def format_date_tz(self, src_date, tz=None):
+        """ This function converts date into specifice timezone value
+        @param src_date: Date to be converted (datetime.datetime)
+        @return: Converted datetime.datetime object for the date
+        """
+        format = tools.DEFAULT_SERVER_DATETIME_FORMAT
+        date_str = src_date.strftime('%Y-%m-%d %H:%M:%S')
+        res_date = tools.server_to_local_timestamp(date_str, format, format, tz)
+        return datetime.strptime(res_date, "%Y-%m-%d %H:%M:%S")
+
+    def parse_ics(self, cr, uid, child, cal_children=None, context=None):
+        """ parse calendaring and scheduling information
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param context: A standard dictionary for contextual values """
+
+        att_data = []
+        exdates = []
+        _server_tzinfo = pytz.timezone(tools.get_server_timezone())
+
+        for cal_data in child.getChildren():
+            if cal_data.name.lower() == 'organizer':
+                dmail = { 'name': cal_data.params.get('CN', ['',])[0],
+                            'email': cal_data.value.lower().replace('mailto:',''),
+                            # TODO: company? 
+                }
+                self.ical_set(cal_data.name.lower(), mailto2str(dmail), 'value')
+                continue
+            if cal_data.name.lower() == 'attendee':
+                ctx = context.copy()
+                if cal_children:
+                    ctx.update({'model': cal_children[cal_data.name.lower()]})
+                attendee = self.pool.get('basic.calendar.attendee')
+                att_data.append(attendee.import_cal(cr, uid, cal_data, context=ctx))
+                self.ical_set(cal_data.name.lower(), att_data, 'value')
+                continue
+            if cal_data.name.lower() == 'valarm':
+                alarm = self.pool.get('basic.calendar.alarm')
+                ctx = context.copy()
+                if cal_children:
+                    ctx.update({'model': cal_children[cal_data.name.lower()]})
+                vals = alarm.import_cal(cr, uid, cal_data, context=ctx)
+                self.ical_set(cal_data.name.lower(), vals, 'value')
+                continue
+            if cal_data.name.lower() == 'exdate':
+                exdates += cal_data.value
+                exvals = []
+                for exdate in exdates:
+                    exvals.append(datetime.fromtimestamp(time.mktime(exdate.utctimetuple())).strftime('%Y%m%dT%H%M%S'))
+                self.ical_set(cal_data.name.lower(), ','.join(exvals), 'value')
+                continue
+            if cal_data.name.lower() in self.__attribute__:
+                if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
+                    self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
+                    date_local = cal_data.value.astimezone(_server_tzinfo)
+                    self.ical_set(cal_data.name.lower(), date_local, 'value')
+                    continue
+                self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
+        vals = map_data(cr, uid, self, context=context)
+        return vals
+
+    def create_ics(self, cr, uid, datas, name, ical, context=None):
+        """ create calendaring and scheduling information
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param context: A standard dictionary for contextual values """
+
+        if not datas:
+            return
+        timezones = []
         for data in datas:
-            vevent = ical.add(vobj)
+            tzval = None
+            exfield = None
+            exdates = []
+            vevent = ical.add(name)
             for field in self.__attribute__.keys():
                 map_field = self.ical_get(field, 'field')
                 map_type = self.ical_get(field, 'type')
                 if map_field in data.keys():
-                    if field == 'uid' :
+                    if field == 'uid':
                         model = context.get('model', None)
                         if not model:
                             continue
-                        uidval = common.openobjectid2uid(cr, data[map_field], model)
+                        uidval = openobjectid2uid(cr, data[map_field], model)
+                        #Computation for getting events with the same UID (RFC4791 Section4.1)
+                        #START
+                        model_obj = self.pool.get(model)
+                        r_ids = []
+                        if model_obj._columns.get('recurrent_uid', None):
+                            cr.execute('SELECT id FROM %s WHERE recurrent_uid=%%s' % model_obj._table,
+                                        (data[map_field],))
+                            r_ids = map(lambda x: x[0], cr.fetchall())
+                        if r_ids:
+                            r_datas = model_obj.read(cr, uid, r_ids, context=context)
+                            rcal = CalDAV.export_cal(self, cr, uid, r_datas, 'vevent', context=context)
+                            for revents in rcal.contents.get('vevent', []):
+                                ical.contents['vevent'].append(revents)
+                        #END
+                        if data.get('recurrent_uid', None):
+                            # Change the UID value in case of modified event from any recurrent event 
+                            uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
                         vevent.add('uid').value = uidval
                     elif field == 'attendee' and data[map_field]:
+                        model = self.__attribute__[field].get('object', False)
                         attendee_obj = self.pool.get('basic.calendar.attendee')
-                        vevent = attendee_obj.export_ical(cr, uid, data[map_field], vevent, context=context)
+                        vevent = attendee_obj.export_cal(cr, uid, model, \
+                                     data[map_field], vevent, context=context)
                     elif field == 'valarm' and data[map_field]:
+                        model = self.__attribute__[field].get('object', False)
+                        ctx = context.copy()
+                        ctx.update({'model': model})
                         alarm_obj = self.pool.get('basic.calendar.alarm')
-                        vevent = alarm_obj.export_ical(cr, uid, data[map_field][0], vevent, context=context)
+                        vevent = alarm_obj.export_cal(cr, uid, model, \
+                                    data[map_field][0], vevent, context=ctx)
+                    elif field == 'vtimezone' and data[map_field]:
+                        tzval = data[map_field]
+                        if tzval not in timezones:
+                            tz_obj = self.pool.get('basic.calendar.timezone')
+                            ical = tz_obj.export_cal(cr, uid, None, \
+                                         data[map_field], ical, context=context)
+                            timezones.append(data[map_field])
+                        if vevent.contents.get('recurrence-id'):
+                            # Convert recurrence-id field value accroding to timezone value
+                            recurid_val = vevent.contents.get('recurrence-id')[0].value
+                            vevent.contents.get('recurrence-id')[0].params['TZID'] = [tzval.title()]
+                            vevent.contents.get('recurrence-id')[0].value =  self.format_date_tz(recurid_val, tzval.title())
+                        if exfield:
+                            # Set exdates according to timezone value
+                            # This is the case when timezone mapping comes after the exdate mapping
+                            # and we have exdate value available 
+                            exfield.params['TZID'] = [tzval.title()]
+                            exdates_updated = []
+                            for exdate in exdates:
+                                exdates_updated.append(self.format_date_tz(parser.parse(exdate), tzval.title()))
+                            exfield.value = exdates_updated
+                    elif field == 'organizer' and data[map_field]:
+                        organizer = str2mailto(data[map_field])
+                        event_org = vevent.add('organizer')
+                        event_org.params['CN'] = [organizer['name']]
+                        event_org.value = 'MAILTO:' + (organizer.get('email') or '')
+                        # TODO: company?
                     elif data[map_field]:
-                        if map_type == "text":
-                            vevent.add(field).value = str(data[map_field])
-                        elif map_type == 'datetime' and data[map_field]:
+                        if map_type in ("char", "text"):
                             if field in ('exdate'):
-                                vevent.add(field).value = [parser.parse(data[map_field])]
+                                exfield = vevent.add(field)
+                                exdates = (data[map_field]).split(',')
+                                if tzval:
+                                    # Set exdates according to timezone value
+                                    # This is the case when timezone mapping comes before the exdate mapping
+                                    # and we have timezone value available 
+                                    exfield.params['TZID'] = [tzval.title()]
+                                    exdates_updated = []
+                                    for exdate in exdates:
+                                        exdates_updated.append(self.format_date_tz(parser.parse(exdate), tzval.title()))
+                                    exfield.value = exdates_updated
                             else:
-                                vevent.add(field).value = parser.parse(data[map_field])
+                                vevent.add(field).value = tools.ustr(data[map_field])
+                        elif map_type in ('datetime', 'date') and data[map_field]:
+                            dtfield = vevent.add(field)
+                            if tzval:
+                                # Export the date according to the event timezone value
+                                dtfield.params['TZID'] = [tzval.title()]
+                                dtfield.value = self.format_date_tz(parser.parse(data[map_field]), tzval.title())
+                            else:
+                                dtfield.value = parser.parse(data[map_field])
+                                
+                        elif map_type == 'utc'and data[map_field]:
+                            if tzval:
+                                local = pytz.timezone (tzval.title())
+                                naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+                                local_dt = naive.replace (tzinfo = local)
+                                utc_dt = local_dt.astimezone (pytz.utc)
+                                vevent.add(field).value = utc_dt
+                            else:
+                               utc_timezone = pytz.timezone ('UTC')
+                               naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+                               local_dt = naive.replace (tzinfo = utc_timezone)
+                               utc_dt = local_dt.astimezone (pytz.utc)
+                               vevent.add(field).value = utc_dt
+
                         elif map_type == "timedelta":
                             vevent.add(field).value = timedelta(hours=data[map_field])
-                        if self.__attribute__.get(field).has_key('mapping'):
-                            for key1, val1 in self.ical_get(field, 'mapping').items():
-                                if val1 == data[map_field]:
-                                    vevent.add(field).value = key1
-        return ical
+                        elif map_type == "many2one":
+                            vevent.add(field).value = tools.ustr(data.get(map_field)[1])
+                        elif map_type in ("float", "integer"):
+                            vevent.add(field).value = str(data.get(map_field))
+                        elif map_type == "selection":
+                            if not self.ical_get(field, 'mapping'):
+                                vevent.add(field).value = (tools.ustr(data[map_field])).upper()
+                            else:
+                                for key1, val1 in self.ical_get(field, 'mapping').items():
+                                    if val1 == data[map_field]:
+                                        vevent.add(field).value = key1.upper()
+        return vevent
+
+    def check_import(self, cr, uid, vals, context=None):
+        """
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param vals: Get Values
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        ids = []
+        model_obj = self.pool.get(context.get('model'))
+        recur_pool = {}
+        try:
+            for val in vals:
+                # Compute value of duration
+                if 'date_deadline' in val and 'duration' not in val:
+                    start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
+                    end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
+                    diff = end - start
+                    val['duration'] = (diff.seconds/float(86400) + diff.days) * 24
+                exists, r_id = calendar.uid2openobjectid(cr, val['id'], context.get('model'), \
+                                                                 val.get('recurrent_id'))
+                if val.has_key('create_date'):
+                    val.pop('create_date')
+                u_id = val.get('id', None)
+                val.pop('id')
+                if exists and r_id:
+                    val.update({'recurrent_uid': exists})
+                    model_obj.write(cr, uid, [r_id], val)
+                    ids.append(r_id)
+                elif exists:
+                    model_obj.write(cr, uid, [exists], val)
+                    ids.append(exists)
+                else:
+                    if u_id in recur_pool and val.get('recurrent_id'):
+                        val.update({'recurrent_uid': recur_pool[u_id]})
+                        revent_id = model_obj.create(cr, uid, val)
+                        ids.append(revent_id)
+                    else:
+                        __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
+                        wematch = __rege.match(u_id.encode('utf8'))
+                        if wematch:
+                            model, recur_id, dbname = wematch.groups()
+                            val.update({'recurrent_uid': recur_id})
+                        event_id = model_obj.create(cr, uid, val)
+                        recur_pool[u_id] = event_id
+                        ids.append(event_id)
+        except Exception:
+            raise
+        return ids
 
-    def import_ical(self, cr, uid, ical_data):
+    def export_cal(self, cr, uid, datas, vobj=None, context=None):
+        """ Export Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param datas: Get Data's for caldav
+            @param context: A standard dictionary for contextual values
+        """
+        try:
+            self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
+            ical = vobject.iCalendar()
+            self.create_ics(cr, uid, datas, vobj, ical, context=context)
+            return ical
+        except:
+            raise  # osv.except_osv(('Error !'), (str(e)))
+
+    def import_cal(self, cr, uid, content, data_id=None, context=None):
+        """ Import Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param data_id: Get Data’s ID or False
+            @param context: A standard dictionary for contextual values
+        """
+
+        ical_data = content
+        self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
         parsedCal = vobject.readOne(ical_data)
-        att_data = []
         res = []
+        vals = {}
         for child in parsedCal.getChildren():
-            for cal_data in child.getChildren():
-                if cal_data.name.lower() == 'attendee':
-                    attendee = self.pool.get('basic.calendar.attendee')
-                    att_data.append(attendee.import_ical(cr, uid, cal_data))
-                    self.ical_set(cal_data.name.lower(), att_data, 'value')
-                    continue
-                if cal_data.name.lower() == 'valarm':
-                    alarm = self.pool.get('basic.calendar.alarm')
-                    vals = alarm.import_ical(cr, uid, cal_data)
-                    self.ical_set(cal_data.name.lower(), vals, 'value')
-                    continue
-                if cal_data.name.lower() in self.__attribute__:
-                    self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
             if child.name.lower() in ('vevent', 'vtodo'):
-                vals = map_data(cr, uid, self)
+                vals = self.parse_ics(cr, uid, child, context=context)
             else:
                 vals = {}
                 continue
@@ -177,9 +568,10 @@ class CalDAV(object):
             self.ical_reset('value')
         return res
 
-
-class Calendar(CalDAV, osv.osv_memory):
+class Calendar(CalDAV, osv.osv):
     _name = 'basic.calendar'
+    _calname = 'calendar'
+
     __attribute__ = {
         'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
         'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
@@ -192,13 +584,340 @@ class Calendar(CalDAV, osv.osv_memory):
         'vjournal': None, # Use: O-n, Type: Collection of Journal class
         'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
         'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
-
     }
+    _columns = {
+            'name': fields.char("Name", size=64),
+            'user_id': fields.many2one('res.users', 'Owner'),
+            'collection_id': fields.many2one('document.directory', 'Collection', \
+                                           required=True),
+            'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
+                                    string="Type", size=64),
+            'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
+            'create_date': fields.datetime('Created Date', readonly=True),
+            'write_date': fields.datetime('Write Date', readonly=True),
+            'description': fields.text("Description"),
+            'calendar_color': fields.char('Color', size=20, help="For supporting clients, the color of the calendar entries"),
+            'calendar_order': fields.integer('Order', help="For supporting clients, the order of this folder among the calendars"),
+            'has_webcal': fields.boolean('WebCal', required=True, help="Also export a <name>.ics entry next to the calendar folder, with WebCal content."),
+    }
+    
+    _defaults = {
+        'has_webcal': False,
+    }
+
+    def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
+        if context is None:
+            context = {}
+        if not domain:
+            domain = []
+        res = []
+        ctx_res_id = context.get('res_id', None)
+        ctx_model = context.get('model', None)
+        for cal in self.browse(cr, uid, ids):
+            for line in cal.line_ids:
+                if ctx_model and ctx_model != line.object_id.model:
+                    continue
+                if line.name in ('valarm', 'attendee'):
+                    continue
+                line_domain = eval(line.domain or '[]', context)
+                line_domain += domain
+                if ctx_res_id:
+                    line_domain += [('id','=',ctx_res_id)]
+                mod_obj = self.pool.get(line.object_id.model)
+                data_ids = mod_obj.search(cr, uid, line_domain, order="id", context=context)
+                for data in mod_obj.browse(cr, uid, data_ids, context):
+                    ctx = parent and parent.context or None
+                    if hasattr(data, 'recurrent_uid') and data.recurrent_uid:
+                        # Skip for event which is child of other event
+                        continue
+                    node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
+                    res.append(node)
+        return res
+        
+
+    def get_cal_max_modified(self, cr, uid, ids, parent=None, domain=None, context=None):
+        if context is None:
+            context = {}
+        if not domain:
+            domain = []
+        res = None
+        ctx_res_id = context.get('res_id', None)
+        ctx_model = context.get('model', None)
+        for cal in self.browse(cr, uid, ids):
+            for line in cal.line_ids:
+                if ctx_model and ctx_model != line.object_id.model:
+                    continue
+                if line.name in ('valarm', 'attendee'):
+                    continue
+                line_domain = eval(line.domain or '[]', context)
+                line_domain += domain
+                if ctx_res_id:
+                    line_domain += [('id','=',ctx_res_id)]
+                mod_obj = self.pool.get(line.object_id.model)
+                max_data = get_last_modified(mod_obj, cr, uid, line_domain, context=context)
+                if res and res > max_data:
+                    continue
+                res = max_data
+        return res
+
+    def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
+        """ Export Calendar
+            @param ids: List of calendar’s IDs
+            @param vobj: the type of object to export
+            @return the ical data.
+        """
+        if context is None:
+           context = {}
+        ctx_model = context.get('model', None)
+        ctx_res_id = context.get('res_id', None)
+        ical = vobject.iCalendar()
+        for cal in self.browse(cr, uid, ids, context=context):
+            for line in cal.line_ids:
+                if ctx_model and ctx_model != line.object_id.model:
+                    continue
+                if line.name in ('valarm', 'attendee'):
+                    continue
+                domain = eval(line.domain or '[]', context)
+                if ctx_res_id:
+                    domain += [('id','=',ctx_res_id)]
+                mod_obj = self.pool.get(line.object_id.model)
+                data_ids = mod_obj.search(cr, uid, domain, context=context)
+                datas = mod_obj.read(cr, uid, data_ids, context=context)
+                context.update({'model': line.object_id.model,
+                                        'calendar_id': cal.id
+                                        })
+                self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
+                self.create_ics(cr, uid, datas, line.name, ical, context=context)
+        return ical.serialize()
+
+    def import_cal(self, cr, uid, content, data_id=None, context=None):
+        """ Import Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param data_id: Get Data’s ID or False
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        vals = []
+        ical_data = content
+        parsedCal = vobject.readOne(ical_data)
+        if not data_id:
+            data_id = self.search(cr, uid, [])[0]
+        cal = self.browse(cr, uid, data_id, context=context)
+        cal_children = {}
+
+        for line in cal.line_ids:
+            cal_children[line.name] = line.object_id.model
+        objs = []
+        checked = True
+        for child in parsedCal.getChildren():
+            if child.name.lower() in cal_children:
+                context.update({'model': cal_children[child.name.lower()],
+                                'calendar_id': cal['id']
+                                })
+                self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
+                val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
+                vals.append(val)
+                objs.append(cal_children[child.name.lower()])
+            elif child.name.upper() == 'CALSCALE':
+                if child.value.upper() != 'GREGORIAN':
+                    self._logger.warning('How do I handle %s calendars?',child.value)
+            elif child.name.upper() in ('PRODID', 'VERSION'):
+                pass
+            elif child.name.upper().startswith('X-'):
+                self._logger.debug("skipping custom node %s", child.name)
+            else:
+                self._logger.debug("skipping node %s", child.name)
+        
+        res = []
+        for obj_name in list(set(objs)):
+            obj = self.pool.get(obj_name)
+            if hasattr(obj, 'check_import'):
+                r = obj.check_import(cr, uid, vals, context=context)
+                checked = True
+                res.extend(r)
+
+        if not checked:
+            r = self.check_import(cr, uid, vals, context=context)
+            res.extend(r)
+        return res
 
 Calendar()
 
+
+class basic_calendar_line(osv.osv):
+    """ Calendar Lines """
+
+    _name = 'basic.calendar.lines'
+    _description = 'Calendar Lines'
+
+    _columns = {
+            'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
+                                    ('valarm', 'Alarm'), \
+                                    ('attendee', 'Attendee')], \
+                                    string="Type", size=64),
+            'object_id': fields.many2one('ir.model', 'Object'),
+            'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
+                                       required=True, ondelete='cascade'),
+            'domain': fields.char('Domain', size=124),
+            'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
+    }
+
+    _defaults = {
+        'domain': lambda *a: '[]',
+    }
+
+    def create(self, cr, uid, vals, context=None):
+        """ create calendar's line
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param vals: Get the Values
+            @param context: A standard dictionary for contextual values
+        """
+
+        cr.execute("SELECT COUNT(id) FROM basic_calendar_lines \
+                                WHERE name=%s AND calendar_id=%s", 
+                                (vals.get('name'), vals.get('calendar_id')))
+        res = cr.fetchone()
+        if res:
+            if res[0] > 0:
+                raise osv.except_osv(_('Warning !'), _('Can not create line "%s" more than once') % (vals.get('name')))
+        return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
+
+basic_calendar_line()
+
+class basic_calendar_alias(osv.osv):
+    """ Mapping of client filenames to ORM ids of calendar records
+    
+        Since some clients insist on putting arbitrary filenames on the .ics data
+        they send us, and they won't respect the redirection "Location:" header, 
+        we have to store those filenames and allow clients to call our calendar
+        records with them.
+        Note that adding a column to all tables that would possibly hold calendar-
+        mapped data won't work. The user is always allowed to specify more 
+        calendars, on any arbitrary ORM object, without need to alter those tables'
+        data or structure
+    """
+    _name = 'basic.calendar.alias'
+    _columns = {
+        'name': fields.char('Filename', size=512, required=True, select=1),
+        'cal_line_id': fields.many2one('basic.calendar.lines', 'Calendar', required=True,
+                        select=1, help='The calendar/line this mapping applies to'),
+        'res_id': fields.integer('Res. ID', required=True, select=1),
+        }
+        
+    _sql_constraints = [ ('name_cal_uniq', 'UNIQUE(cal_line_id, name)',
+                _('The same filename cannot apply to two records!')), ]
+
+basic_calendar_alias()
+
+class basic_calendar_attribute(osv.osv):
+    _name = 'basic.calendar.attributes'
+    _description = 'Calendar attributes'
+    _columns = {
+        'name': fields.char("Name", size=64, required=True),
+        'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
+                                    ('alarm', 'Alarm'), \
+                                    ('attendee', 'Attendee')], \
+                                    string="Type", size=64, required=True),
+    }
+
+basic_calendar_attribute()
+
+
+class basic_calendar_fields(osv.osv):
+    """ Calendar fields """
+
+    _name = 'basic.calendar.fields'
+    _description = 'Calendar fields'
+    _order = 'name'
+
+    _columns = {
+        'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
+        'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
+        'type_id': fields.many2one('basic.calendar.lines', 'Type', \
+                                   required=True, ondelete='cascade'),
+        'expr': fields.char("Expression", size=64),
+        'fn': fields.selection([('field', 'Use the field'),
+                        ('const', 'Expression as constant'),
+                        ('hours', 'Interval in hours'),
+                        ('datetime_utc', 'Datetime In UTC'),
+                        ], 'Function'),
+        'mapping': fields.text('Mapping'),
+    }
+
+    _defaults = {
+        'fn': 'field',
+    }
+
+    _sql_constraints = [
+        ( 'name_type_uniq', 'UNIQUE(name, type_id)', 'Can not map a field more than once'),
+    ]
+
+    def check_line(self, cr, uid, vals, name, context=None):
+        """ check calendar's line
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param vals: Get Values
+            @param context: A standard dictionary for contextual values
+        """
+        f_obj = self.pool.get('ir.model.fields')
+        field = f_obj.browse(cr, uid, vals['field_id'], context=context)
+        relation = field.relation
+        line_obj = self.pool.get('basic.calendar.lines')
+        l_id = line_obj.search(cr, uid, [('name', '=', name)])
+        if l_id:
+            line = line_obj.browse(cr, uid, l_id, context=context)[0]
+            line_rel = line.object_id.model
+            if (relation != 'NULL') and (not relation == line_rel):
+                raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines') % (name))
+        return True
+
+    def create(self, cr, uid, vals, context=None):
+        """ Create Calendar's fields
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param vals: Get Values
+            @param context: A standard dictionary for contextual values
+        """
+
+        cr.execute('SELECT name FROM basic_calendar_attributes \
+                            WHERE id=%s', (vals.get('name'),))
+        name = cr.fetchone()
+        name = name[0]
+        if name in ('valarm', 'attendee'):
+            self.check_line(cr, uid, vals, name, context=context)
+        return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
+
+    def write(self, cr, uid, ids, vals, context=None):
+        """ write Calendar's fields
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param vals: Get Values
+            @param context: A standard dictionary for contextual values
+        """
+
+        if not vals:
+            return
+        for id in ids:
+            field = self.browse(cr, uid, id, context=context)
+            name = field.name.name
+            if name in ('valarm', 'attendee'):
+                self.check_line(cr, uid, vals, name, context=context)
+        return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
+
+basic_calendar_fields()
+
+
 class Event(CalDAV, osv.osv_memory):
     _name = 'basic.calendar.event'
+    _calname = 'vevent'
     __attribute__ = {
         'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar  component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
         '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.
@@ -216,7 +935,7 @@ class Event(CalDAV, osv.osv_memory):
         'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
         'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
         'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
-        'recurid': None, 
+        'recurid': None,
         'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
         'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
         'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
@@ -224,68 +943,89 @@ class Event(CalDAV, osv.osv_memory):
         'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a  reference to contact information associated with the calendar component.
         'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
         'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
-        'rstatus': None, 
+        'rstatus': None,
         'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
                                 #  like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
         '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
         'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
         'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
-        'x-prop': None, 
+        'x-prop': None,
         'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
         'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
     }
-    def export_ical(self, cr, uid, datas, context={}):
-        return super(Event, self).export_ical(cr, uid, datas, 'vevent', context=context)
-    
+
+    def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
+        """ Export calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param datas: Get datas
+            @param context: A standard dictionary for contextual values
+        """
+
+        return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
+
 Event()
 
+
 class ToDo(CalDAV, osv.osv_memory):
     _name = 'basic.calendar.todo'
+    _calname = 'vtodo'
 
     __attribute__ = {
-                'class': None, 
-                'completed': None, 
-                'created': None, 
-                'description': None, 
-                'dtstamp': None, 
-                'dtstart': None, 
+                'class': None,
+                'completed': None,
+                'created': None,
+                'description': None,
+                'dtstamp': None,
+                'dtstart': None,
                 'duration': None,
                 'due': None,
-                'geo': None, 
-                'last-mod ': None, 
-                'location': None, 
-                'organizer': None, 
-                'percent': None, 
-                'priority': None, 
-                'recurid': None, 
-                'seq': None, 
-                'status': None, 
-                'summary': None, 
-                'uid': None, 
-                'url': None, 
-                'attach': None, 
-                'attendee': None, 
-                'categories': None, 
-                'comment': None, 
-                'contact': None, 
-                'exdate': None, 
-                'exrule': None, 
-                'rstatus': None, 
-                'related': None, 
-                'resources': None, 
-                'rdate': None, 
-                'rrule': None, 
+                'geo': None,
+                'last-mod ': None,
+                'location': None,
+                'organizer': None,
+                'percent': None,
+                'priority': None,
+                'recurid': None,
+                'seq': None,
+                'status': None,
+                'summary': None,
+                'uid': None,
+                'url': None,
+                'attach': None,
+                'attendee': None,
+                'categories': None,
+                'comment': None,
+                'contact': None,
+                'exdate': None,
+                'exrule': None,
+                'rstatus': None,
+                'related': None,
+                'resources': None,
+                'rdate': None,
+                'rrule': None,
             }
-    
-    def export_ical(self, cr, uid, datas, context={}):
-        return super(ToDo, self).export_ical(cr, uid, datas, 'vtodo', context=context)
+
+    def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
+        """ Export Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param datas: Get datas
+            @param context: A standard dictionary for contextual values
+        """
+
+        return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
 
 ToDo()
 
+
 class Journal(CalDAV):
     __attribute__ = {
     }
 
+
 class FreeBusy(CalDAV):
     __attribute__ = {
     'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a  reference to contact information associated with the calendar component.
@@ -299,14 +1039,17 @@ class FreeBusy(CalDAV):
     'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
     'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
     'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
-    'rstatus': None, 
-    'X-prop': None, 
+    'rstatus': None,
+    'X-prop': None,
     }
 
 
-class Timezone(CalDAV):
+class Timezone(CalDAV, osv.osv_memory):
+    _name = 'basic.calendar.timezone'
+    _calname = 'vtimezone'
+
     __attribute__ = {
-    'tzid': None, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
+    'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
     '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.
     '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.
     'standardc': {'tzprop': None}, # Use: R-1,
@@ -314,9 +1057,69 @@ class Timezone(CalDAV):
     'x-prop': None, # Use: O-n, Type: Text,
     }
 
+    def get_name_offset(self, cr, uid, tzid, context=None):
+        """ Get Name Offset value
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param context: A standard dictionary for contextual values
+        """
+
+        mytz = pytz.timezone(tzid.title())
+        mydt = datetime.now(tz=mytz)
+        offset = mydt.utcoffset()
+        val = offset.days * 24 + float(offset.seconds) / 3600
+        realoffset = '%02d%02d' % (math.floor(abs(val)), \
+                                 round(abs(val) % 1 + 0.01, 2) * 60)
+        realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
+        return (mydt.tzname(), realoffset)
+
+    def export_cal(self, cr, uid, model, tzid, ical, context=None):
+        """ Export Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param model: Get Model's name
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        ctx = context.copy()
+        ctx.update({'model': model})
+        cal_tz = ical.add('vtimezone')
+        cal_tz.add('TZID').value = tzid.title()
+        tz_std = cal_tz.add('STANDARD')
+        tzname, offset = self.get_name_offset(cr, uid, tzid)
+        tz_std.add("TZOFFSETFROM").value = offset
+        tz_std.add("TZOFFSETTO").value = offset
+        #TODO: Get start date for timezone
+        tz_std.add("DTSTART").value = datetime.strptime('1970-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')
+        tz_std.add("TZNAME").value = tzname
+        return ical
+
+    def import_cal(self, cr, uid, ical_data, context=None):
+        """ Import Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param ical_data: Get calendar's data
+            @param context: A standard dictionary for contextual values
+        """
+
+        for child in ical_data.getChildren():
+            if child.name.lower() == 'tzid':
+                tzname = child.value
+                self.ical_set(child.name.lower(), tzname, 'value')
+        vals = map_data(cr, uid, self, context=context)
+        return vals
+
+Timezone()
+
 
 class Alarm(CalDAV, osv.osv_memory):
     _name = 'basic.calendar.alarm'
+    _calname = 'alarm'
+
     __attribute__ = {
     'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
     '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
@@ -326,21 +1129,29 @@ class Alarm(CalDAV, osv.osv_memory):
     '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
     '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
     '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.
-    'x-prop': None, 
+    'x-prop': None,
     }
 
-    def export_ical(self, cr, uid, alarm_id, vevent, context={}):
+    def export_cal(self, cr, uid, model, alarm_id, vevent, context=None):
+        """ Export Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param model: Get Model's name
+            @param alarm_id: Get Alarm's Id
+            @param context: A standard dictionary for contextual values
+        """
         valarm = vevent.add('valarm')
-        alarm_object = self.pool.get('calendar.alarm')
+        alarm_object = self.pool.get(model)
         alarm_data = alarm_object.read(cr, uid, alarm_id, [])
 
         # Compute trigger data
         interval = alarm_data['trigger_interval']
         occurs = alarm_data['trigger_occurs']
         duration = (occurs == 'after' and alarm_data['trigger_duration']) \
-                                            or -(alarm_data['trigger_duration'])
+                                        or -(alarm_data['trigger_duration'])
         related = alarm_data['trigger_related']
-        trigger = valarm.add('trigger')
+        trigger = valarm.add('TRIGGER')
         trigger.params['related'] = [related.upper()]
         if interval == 'days':
             delta = timedelta(days=duration)
@@ -351,29 +1162,46 @@ class Alarm(CalDAV, osv.osv_memory):
         trigger.value = delta
 
         # Compute other details
-        valarm.add('description').value = alarm_data['name']
-        valarm.add('action').value = alarm_data['action']
+        valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
+        valarm.add('ACTION').value = alarm_data['action']
         return vevent
-        
-    def import_ical(self, cr, uid, ical_data):
+
+    def import_cal(self, cr, uid, ical_data, context=None):
+        """ Import Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param ical_data: Get calendar's Data
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        ctx = context.copy()
+        ctx.update({'model': context.get('model', None)})
+        self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
         for child in ical_data.getChildren():
             if child.name.lower() == 'trigger':
-                seconds = child.value.seconds
-                days = child.value.days
-                diff = (days * 86400) +  seconds
-                interval = 'days'
-                related = 'before'
+                if isinstance(child.value, timedelta):
+                    seconds = child.value.seconds
+                    days = child.value.days
+                    diff = (days * 86400) +  seconds
+                    interval = 'days'
+                    related = 'before'
+                elif isinstance(child.value, datetime):
+                    # TODO
+                    # remember, spec says this datetime is in UTC
+                    raise NotImplementedError("we cannot parse absolute triggers")
                 if not seconds:
                     duration = abs(days)
-                    related = days>0 and 'after' or 'before'
+                    related = days > 0 and 'after' or 'before'
                 elif (abs(diff) / 3600) == 0:
                     duration = abs(diff / 60)
                     interval = 'minutes'
-                    related = days>=0 and 'after' or 'before'
+                    related = days >= 0 and 'after' or 'before'
                 else:
                     duration = abs(diff / 3600)
                     interval = 'hours'
-                    related = days>=0 and 'after' or 'before'
+                    related = days >= 0 and 'after' or 'before'
                 self.ical_set('trigger_interval', interval, 'value')
                 self.ical_set('trigger_duration', duration, 'value')
                 self.ical_set('trigger_occurs', related.lower(), 'value')
@@ -382,13 +1210,16 @@ class Alarm(CalDAV, osv.osv_memory):
                         self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
             else:
                 self.ical_set(child.name.lower(), child.value.lower(), 'value')
-        vals = map_data(cr, uid, self)
+        vals = map_data(cr, uid, self, context=context)
         return vals
 
 Alarm()
 
+
 class Attendee(CalDAV, osv.osv_memory):
     _name = 'basic.calendar.attendee'
+    _calname = 'attendee'
+
     __attribute__ = {
     'cutype': None, # Use: 0-1    Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
     'member': None, # Use: 0-1    Specify the group or list membership of the calendar user specified by the property.
@@ -403,30 +1234,65 @@ class Attendee(CalDAV, osv.osv_memory):
     'language': None, # Use: 0-1    Specify the language for text values in a property or property parameter.
     }
 
-    def import_ical(self, cr, uid, ical_data):
+    def import_cal(self, cr, uid, ical_data, context=None):
+        """ Import Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param ical_data: Get calendar's Data
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        ctx = context.copy()
+        ctx.update({'model': context.get('model', None)})
+        self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
         for para in ical_data.params:
             if para.lower() == 'cn':
-                self.ical_set(para.lower(), ical_data.params[para][0]+':'+ ical_data.value, 'value')
+                self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
+                        ical_data.value, 'value')
             else:
                 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
         if not ical_data.params.get('CN'):
             self.ical_set('cn', ical_data.value, 'value')
-        vals = map_data(cr, uid, self)
+        vals = map_data(cr, uid, self, context=context)
         return vals
 
-    def export_ical(self, cr, uid, attendee_id, vevent, context={}):
-        attendee_object = self.pool.get('calendar.attendee')
-        for attendee in attendee_object.read(cr, uid, attendee_id, []):
+    def export_cal(self, cr, uid, model, attendee_ids, vevent, context=None):
+        """ Export Calendar
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param model: Get model's name
+            @param attendee_ids: Get Attendee's Id
+            @param context: A standard dictionary for contextual values
+        """
+        if context is None:
+            context = {}
+        attendee_object = self.pool.get(model)
+        ctx = context.copy()
+        ctx.update({'model': model})
+        self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
+        for attendee in attendee_object.read(cr, uid, attendee_ids, []):
             attendee_add = vevent.add('attendee')
-            for a_key, a_val in attendee_object.__attribute__.items():
-                if attendee[a_val['field']]:
-                    if a_val['type'] == 'text':
+            cn_val = ''
+            for a_key, a_val in self.__attribute__.items():
+                if attendee[a_val['field']] and a_val['field'] != 'cn':
+                    if a_val['type'] in ('text', 'char', 'selection'):
                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
                     elif a_val['type'] == 'boolean':
                         attendee_add.params[a_key] = [str(attendee[a_val['field']])]
+                if a_val['field'] == 'cn' and attendee[a_val['field']]:
+                    cn_val = [str(attendee[a_val['field']])]
+                    if cn_val:
+                        attendee_add.params['CN'] = cn_val
+            if not attendee['email']:
+                attendee_add.value = 'MAILTO:'
+                #raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
+            elif attendee['email']:
+                attendee_add.value = 'MAILTO:' + attendee['email']
         return vevent
 
 Attendee()
 
-
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: