[ADD]: Images: caldav, crm_caldav, document_webdav, project_caldav
[odoo/odoo.git] / addons / caldav / calendar.py
index aeac98e..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')
         field = obj.ical_get(map_dict, 'field')
         field_type = obj.ical_get(map_dict, 'type')
-        if field and map_val:
+        if field:
             if field_type == 'selection':
-                mapping =obj.__attribute__[map_dict].get('mapping', False)
+                if not map_val:
+                    continue
+                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]
+                    map_val = mapping.get(map_val.lower(), False)
+                else:
+                    map_val = map_val.lower()
             if field_type == 'many2many':
                 ids = []
+                if not map_val:
+                    vals[field] = ids
+                    continue
                 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 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 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):
-        def todate(date):
-            val = parser.parse(''.join((re.compile('\d')).findall(date)) + 'Z')
-            return val
-
-        if not startdate:
-            startdate = datetime.now()
-        else:
-            startdate = todate(startdate)
-        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]:
-                        attendee_obj = self.pool.get('caldav.attendee')
-                        vevent = attendee_obj.export_ical(cr, uid, data[map_field], vevent, context=context)
+                        model = self.__attribute__[field].get('object', False)
+                        attendee_obj = self.pool.get('basic.calendar.attendee')
+                        vevent = attendee_obj.export_cal(cr, uid, model, \
+                                     data[map_field], vevent, context=context)
                     elif field == 'valarm' and data[map_field]:
-                        alarm_obj = self.pool.get('caldav.alarm')
-                        vevent = alarm_obj.export_ical(cr, uid, data[map_field][0], vevent, context=context)
+                        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_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 = 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:
-                                vevent.add(field).value = parser.parse(data[map_field])
+                               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 import_ical(self, cr, uid, ical_data):
+    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 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('caldav.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('caldav.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')
-            vals = map_data(cr, uid, self)
+            if child.name.lower() in ('vevent', 'vtodo'):
+                vals = self.parse_ics(cr, uid, child, context=context)
+            else:
+                vals = {}
+                continue
             if vals: res.append(vals)
             self.ical_reset('value')
         return res
 
+class Calendar(CalDAV, osv.osv):
+    _name = 'basic.calendar'
+    _calname = 'calendar'
 
-class Calendar(CalDAV, osv.osv_memory):
-    _name = 'caldav.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
@@ -182,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 = 'caldav.event'
+    _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.
@@ -206,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.
@@ -214,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
+                                #  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 = 'caldav.todo'
+    _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.
@@ -289,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,
@@ -304,81 +1057,169 @@ 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 = 'caldav.alarm'
+    _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
     'summary': None, # Use: R-1, Type: Text        Which contains the text to be used as the message subject. Use for EMAIL
     '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
-    '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
+    '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
     '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('crm.caldav.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'])
+        duration = (occurs == 'after' and alarm_data['trigger_duration']) \
+                                        or -(alarm_data['trigger_duration'])
         related = alarm_data['trigger_related']
         trigger = valarm.add('TRIGGER')
-        trigger.params['RELATED'] = [related.upper()]
-        if interval == 'DAYS':
+        trigger.params['related'] = [related.upper()]
+        if interval == 'days':
             delta = timedelta(days=duration)
-        if interval == 'HOURS':
+        if interval == 'hours':
             delta = timedelta(hours=duration)
-        if interval == 'MINUTES':
+        if interval == 'minutes':
             delta = timedelta(minutes=duration)
         trigger.value = delta
 
         # Compute other details
-        valarm.add('DESCRIPTION').value = alarm_data['name']
+        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'
+                    interval = 'minutes'
+                    related = days >= 0 and 'after' or 'before'
                 else:
                     duration = abs(diff / 3600)
-                    interval = 'HOURS'
-                    related = days>=0 and 'AFTER' or 'BEFORE'
+                    interval = 'hours'
+                    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, 'value')
+                self.ical_set('trigger_occurs', related.lower(), 'value')
                 if child.params:
-                    if child.params.get('RELATED'):
-                        self.ical_set('trigger_related', child.params.get('RELATED')[0].lower(), 'value')
+                    if child.params.get('related'):
+                        self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
             else:
-                self.ical_set(child.name.lower(), child.value, 'value')
-        vals = map_data(cr, uid, self)
+                self.ical_set(child.name.lower(), child.value.lower(), 'value')
+        vals = map_data(cr, uid, self, context=context)
         return vals
 
 Alarm()
 
+
 class Attendee(CalDAV, osv.osv_memory):
-    _name = 'caldav.attendee'
+    _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.
@@ -393,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], 'value')
+                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('crm.caldav.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: