document: fix regressions at storage and node_descriptor
[odoo/odoo.git] / addons / caldav / calendar.py
index 60d0a65..ccc4bf3 100644 (file)
-# -*- encoding: utf-8 -*-
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
-#    $Id$
+#    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 General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
 #
 #    This program is distributed in the hope that it will be useful,
 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
+#    GNU Affero General Public License for more details.
 #
-#    You should have received a copy of the GNU General Public License
+#    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/>.
 #
 ##############################################################################
 
-from osv import fields, osv
-from time import strftime
-import time
-import base64
-import vobject
-from dateutil.rrule import *
+from datetime import datetime, timedelta
 from dateutil import parser
-from datetime import datetime
-from time import strftime
-from pytz import timezone
+from dateutil.rrule import *
+from osv import osv, fields
+from tools.translate import _
+import math
+import pooler
+import pytz
+import re
+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
 
-class CalDAV(object):
-    __attribute__= {
-    }
+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)
 
-    def ical_items(self):
-        return self.__attribute__.items()
+    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 not context:
+        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):
+        attr = field.name.name
+        res[attr] = {}
+        res[attr]['field'] = field.field_id.name
+        res[attr]['type'] = field.field_id.ttype
+        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:
+            if field_type == 'selection':
+                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.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, 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)
+                # 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__ = {}
+    _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
+            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:
-                     val = val.strftime('%Y-%m-%d %H:%M:%S')
-                if valtype and valtype=='integer' and val:
-                     val = int(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')
             return  val
         else:
-             return  self.__attribute__.get(name, None)
+            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 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 """
 
-    def export_ical(self):
-        pass
+        att_data = []
+        exdates = []
+        _server_tzinfo = pytz.timezone(tools.get_server_timezone())
 
-    def import_ical(self, cr, uid, ical_data):
+        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:
+            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':
+                        model = context.get('model', None)
+                        if not model:
+                            continue
+                        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_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_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 in ("char", "text"):
+                            if field in ('exdate'):
+                                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 == "timedelta":
+                            vevent.add(field).value = timedelta(hours=data[map_field])
+                        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 not context:
+            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)
+        res = []
+        vals = {}
         for child in parsedCal.getChildren():
-            for cal_data in child.getChildren():
-                if cal_data.name.lower() == 'attendee':
-                    # This is possible only if Attendee does not inherit osv_memory
-                    attendee = Attendee()
-                    attendee.import_ical(cr, uid, cal_data)
-                if cal_data.name.lower() in self.__attribute__:
-                    self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
-        return True
+            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
+        '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
                            #             or the minimum and maximum range of the iCalendar specification
                            #             that is required in order to interpret the iCalendar object.
-        'calscale' : None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
-        'method' : None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
-        'vevent'  : None, # Use: O-n, Type: Collection of Event class
-        'vtodo'   : None, # Use: O-n, Type: Collection of ToDo class
+        'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
+        'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
+        'vevent': None, # Use: O-n, Type: Collection of Event class
+        'vtodo': None, # Use: O-n, Type: Collection of ToDo class
         '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('Modifided 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 not context:
+            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 not context:
+            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 import_ical(self, cr, uid, ical_data):
-        # Write openobject data from ical_data
-        ical = vobject.readOne(ical_data)
-        for child in ical.getChildren():
-            child_name = child.name.lower()
-            if child_name == 'vevent':
-                for event in child.getChildren():
-                    if event.name.lower() =="attendee":
-                        attendee = Attendee()
-                        attendee.import_ical(cr, uid, event)
-                    if event.name.lower() =="valarm":
-                        alarm = Alarm()
-                        alarm.import_ical(cr, uid, event)
-            elif child_name == "vtimezone":
-                timezone = Timezone()
-                timezone.import_ical(cr, uid, child)
-        return True        
-
-    def export_ical(self, cr, uid, ids):
-        # Read openobject data in ical format
+    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 not context:
+           context = {}
+        ctx_model = context.get('model', None)
+        ctx_res_id = context.get('res_id', None)
         ical = vobject.iCalendar()
-        datas = self.browse(cr, uid, ids)
+        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
+                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()
 
-        vcal = ical.add('vcalendar')
-        for name, value in self.ical_items():
-            if not value:
-                continue
-            if name == 'vevent':
-                for event in value:
-                    vevent = event.ical_read()
-                    vcal.add(vevent)
-            elif name == 'vtodo':
-                for todo in value:
-                    vtodo = todo.ical_read()
-                    vcal.add(vtodo)
-            elif name == 'vjournal':
-                for journal in value:
-                    vjournal = journal.ical_read()
-                    vcal.add(vjournal)
-            elif name == 'vfreebusy':
-                for freebusy in value:
-                    vfreebusy = freebusy.ical_read()
-                    vcal.add(vfreebusy)
-            elif name == 'vtimezone':
-                for timezone in value:
-                    vtimezone = timezone.ical_read()
-                    vcal.add(vtimezone)
-            else:
-                vcal.add(name).value = value
-        s = ical.serialize()
-        return s
-
-    def ical_write(self, data):
-        ical = vobject.readOne(data)
-        for child in ical.getChildren():
-            child_name = child.name.lower()
-            child_value = child.value
-            if child_name == 'vevent':
-                vevents = []
-                for event in child.getChildren():
-                    vevent = Event()
-                    vevent.ical_write(event.serialize())
-                    vevents.append(vevent)
-                self.ical_set(child_name, vevents)
+    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 not context:
+            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.ical_set(child_name, child_value)
-        return True
+                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'
+
+    _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'),
+                        ], 'Function'),
+        'mapping': fields.text('Mapping'),
+    }
+
+    _defaults = {
+        'fn': lambda *a: '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.
-        'description' : None, # Use: O-1, Type: TEXT,            Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
-        'dtstart' : None, # Use: O-1, Type: DATE-TIME,    Specifies when the calendar component begins.
-        'geo' : None, # Use: O-1, Type: FLOAT,        Specifies information related to the global position for the activity specified by a 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.
-        'location' : None, # Use: O-1, Type: TEXT            Defines the intended venue for the activity defined by a calendar component.
-        'organizer' : None, # Use: O-1, Type: CAL-ADDRESS,  Defines the organizer for a calendar component.
-        'priority' : None, # Use: O-1, Type: INTEGER,      Defines the relative priority for a calendar component.
-        'dtstamp'  : None, # Use: O-1, Type: DATE-TIME,    Indicates the date/time that the instance of the iCalendar object was created.
-        'seq' : None, # Use: O-1, Type: INTEGER,      Defines the revision sequence number of the calendar component within a sequence of revision.
-        'status' : None, # Use: O-1, Type: TEXT,            Defines the overall status or confirmation for the calendar component.
-        'summary' : None, # Use: O-1, Type: TEXT,            Defines a short summary or subject for the calendar component.
-        '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, 
-        '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.
-        'comment' : None, # Use: O-n,    Type: TEXT,            Specifies non-processing information intended to provide a comment to the calendar user.
-        '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, 
-        '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, 
-        '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.
+        '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.
+        'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
+        'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
+        'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a 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.
+        'location': None, # Use: O-1, Type: TEXT            Defines the intended venue for the activity defined by a calendar component.
+        'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
+        'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
+        'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
+        'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
+        'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
+        'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
+        '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,
+        '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.
+        'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
+        '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,
+        '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,
+        '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 get_recurrent_dates(self, rrulestring, exdate, startdate=None):
-        todate = parser.parse
-        if not startdate:
-            startdate = datetime.now()
-        else:
-            startdate = todate(startdate)
-        rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
-        for date in exdate:
-            datetime_obj = datetime.strptime(date, "%Y-%m-%d %H:%M:%S", timezone=True)
-#            datetime_obj_utc = datetime_obj.replace(tzinfo=timezone('UTC'))
-            rset1._exdate.append(datetime_obj)
-        re_dates = rset1._iter()
-        recurrent_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S', timezone=True), re_dates)
-        return recurrent_dates
-
-    def search(self, cr, uid, args, offset=0, limit=None, order=None, 
-            context=None, count=False):
-        # put logic for recurrent event
-        # example : 123-20091111170822
-        pass
-
-    def create(self, cr, uid, vals, context={}):
-        # put logic for recurrent event
-        # example : 123-20091111170822
-        pass
-
-    def write(self, cr, uid, ids, vals, context={}):
-        # put logic for recurrent event
-#        # example : 123-20091111170822
-        pass
+    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):
+
+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,
+                '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,
+            }
+
+    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.
-    'dtstart' : None, # Use: O-1, Type: DATE-TIME,    Specifies when the calendar component begins.
-    'dtend' : None, # Use: O-1, Type: DATE-TIME,    Specifies the date and time that a calendar component ends.
-    'duration' : None, # Use: O-1, Type: DURATION,     Specifies a positive duration of time.
-    'dtstamp' : None, # Use: O-1, Type: DATE-TIME,    Indicates the date/time that the instance of the iCalendar object was created.
-    'organizer' : None, # Use: O-1, Type: CAL-ADDRESS,  Defines the organizer for a calendar component.
-    '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.
-    '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, 
+    __attribute__ = {
+    'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a  reference to contact information associated with the calendar component.
+    'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
+    'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
+    'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
+    'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
+    'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
+    '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.
+    '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,
     }
 
-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.
-    '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' :           # Use: R-n,
-        {'tzprop' : None, }, # Use: R-1,
-    'daylightc' :           # Use: R-n, Type: Text,
-        {'tzprop' : None, }, # Use: R-1,
-    'x-prop' : None, # Use: O-n, Type: Text,
+    '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,
+    'daylightc': {'tzprop': None}, # Use: R-1,
+    'x-prop': None, # Use: O-n, Type: Text,
     }
-    def import_ical(self, cr, uid, ical_data):
-        for val in ical_data.getChildren():
-            if self.__attribute__.has_key(val.name.lower()):
-                self.__attribute__[val.name] = val.value
 
-class Alarm(CalDAV):
+    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 not context:
+            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
-    '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
-    '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, 
+    '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
+    '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,
     }
-    
-    def import_ical(self, cr, uid, ical_data):
-        for val in ical_data.getChildren():
-            if self.__attribute__.has_key(val.name.lower()):
-                self.__attribute__[val.name] = val.value
-
-class Attendee(CalDAV):
-#  Also inherit osv_memory
-    _name = 'caldav.attendee'
+
+    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
+        """
+        if not context:
+            context = {}
+        valarm = vevent.add('valarm')
+        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'])
+        related = alarm_data['trigger_related']
+        trigger = valarm.add('TRIGGER')
+        trigger.params['related'] = [related.upper()]
+        if interval == 'days':
+            delta = timedelta(days=duration)
+        if interval == 'hours':
+            delta = timedelta(hours=duration)
+        if interval == 'minutes':
+            delta = timedelta(minutes=duration)
+        trigger.value = delta
+
+        # Compute other details
+        valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
+        valarm.add('ACTION').value = alarm_data['action']
+        return vevent
+
+    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
+        """
+
+        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 not seconds:
+                    duration = abs(days)
+                    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'
+                else:
+                    duration = abs(diff / 3600)
+                    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.lower(), 'value')
+                if child.params:
+                    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.lower(), 'value')
+        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.
-    'role' : None, # Use: 0-1    Specify the participation role for the calendar user specified by the property like "CHAIR"/"REQ-PARTICIPANT"/"OPT-PARTICIPANT"/"NON-PARTICIPANT"
-    'partstat' : None, # Use: 0-1    Specify the participation status for the calendar user specified by the property. like use for VEVENT :- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED", use for VTODO :-"NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED"/"COMPLETED"/"IN-PROCESS" and use for VJOURNAL :- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED".
-    'rsvp' : None, # Use: 0-1    Specify whether there is an expectation of a favor of a reply from the calendar user specified by the property value like TRUE / FALSE.
-    'delegated-to' : None, # Use: 0-1    Specify the calendar users to whom the calendar user specified by the property has delegated participation.
-    'delegated-from' : None, # Use: 0-1    Specify the calendar users that have delegated their participation to the calendar user specified by the property.
-    'sent-by' : None, # Use: 0-1    Specify the calendar user that is acting on behalf of the calendar user specified by the property.
-    'cn' : None, # Use: 0-1    Specify the common name to be associated with the calendar user specified by the property.
-    'dir' : None, # Use: 0-1    Specify reference to a directory entry associated with the calendar user specified by the property.
-    'language' : None, # Use: 0-1    Specify the language for text values in a property or property parameter.
+    '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.
+    'role': None, # Use: 0-1    Specify the participation role for the calendar user specified by the property like "CHAIR"/"REQ-PARTICIPANT"/"OPT-PARTICIPANT"/"NON-PARTICIPANT"
+    'partstat': None, # Use: 0-1    Specify the participation status for the calendar user specified by the property. like use for VEVENT:- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED", use for VTODO:-"NEEDS-ACTION"/"ACCEPTED"/"DECLINED"/"TENTATIVE"/"DELEGATED"/"COMPLETED"/"IN-PROCESS" and use for VJOURNAL:- "NEEDS-ACTION"/"ACCEPTED"/"DECLINED".
+    'rsvp': None, # Use: 0-1    Specify whether there is an expectation of a favor of a reply from the calendar user specified by the property value like TRUE / FALSE.
+    'delegated-to': None, # Use: 0-1    Specify the calendar users to whom the calendar user specified by the property has delegated participation.
+    'delegated-from': None, # Use: 0-1    Specify the calendar users that have delegated their participation to the calendar user specified by the property.
+    'sent-by': None, # Use: 0-1    Specify the calendar user that is acting on behalf of the calendar user specified by the property.
+    'cn': None, # Use: 0-1    Specify the common name to be associated with the calendar user specified by the property.
+    'dir': None, # Use: 0-1    Specify reference to a directory entry associated with the calendar user specified by the property.
+    '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):
-        if ical_data.value:
-            self.__attribute__['sent-by'] = ical_data.value
-        for key, val in ical_data.params.items():
-            if self.__attribute__.has_key(key.lower()):
-                if type(val) == type([]):
-                    self.__attribute__[key] = val[0]
-                else:
-                    self.__attribute__[key] = val
-        return
+    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
+        """
+
+        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')
+            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, context=context)
+        return vals
+
+    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 not context:
+            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')
+            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: