# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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
-import common
+from osv import osv, fields
+from tools.translate import _
+import math
+import pooler
+import pytz
import re
-import vobject
+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')
if field_type == 'selection':
if not map_val:
continue
- mapping =obj.__attribute__[map_dict].get('mapping', False)
+ if type(map_val) == list and len(map_val): #TOFIX: why need to check this
+ map_val = map_val[0]
+ mapping = obj.__attribute__[map_dict].get('mapping', False)
if mapping:
- map_val = mapping[map_val.lower()]
+ map_val = mapping.get(map_val.lower(), False)
else:
map_val = map_val.lower()
if field_type == 'many2many':
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
continue
model = obj.__attribute__[map_dict].get('object', False)
modobj = obj.pool.get(model)
- id = modobj.create(cr, uid, map_val)
+ # check if the record exists or not
+ key1 = map_val.keys()
+ value1 = map_val.values()
+ domain = [(key1[i], '=', value1[i]) for i in range(len(key1)) if value1[i]]
+ exist_id = modobj.search(cr, uid, domain, context=context)
+ if exist_id:
+ id = exist_id[0]
+ else:
+ id = modobj.create(cr, uid, map_val, context=context)
vals[field] = id
continue
- if map_val:
- vals[field] = map_val
+ if field_type == 'timedelta':
+ if map_val:
+ vals[field] = (map_val.seconds/float(86400) + map_val.days)
+ vals[field] = map_val
return vals
class CalDAV(object):
- __attribute__ = {
- }
- def get_recurrent_dates(self, rrulestring, exdate, startdate=None):
- if not startdate:
- startdate = datetime.now()
- rset1 = rrulestr(rrulestring, dtstart=startdate, forceset=True)
-
- for date in exdate:
- datetime_obj = todate(date)
- rset1._exdate.append(datetime_obj)
- re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
- return re_dates
+ __attribute__ = {}
+ _logger = logging.getLogger('document.caldav')
def ical_set(self, name, value, type):
+ """ set calendar Attribute
+ @param self: The object pointer,
+ @param name: Get Attribute Name
+ @param value: Get Attribute Value
+ @param type: Get Attribute Type
+ """
if name in self.__attribute__ and self.__attribute__[name]:
self.__attribute__[name][type] = value
return True
def ical_get(self, name, type):
+ """ Get calendar Attribute
+ @param self: The object pointer,
+ @param name: Get Attribute Name
+ @param type: Get Attribute Type
+ """
if self.__attribute__.get(name):
val = self.__attribute__.get(name).get(type, None)
valtype = self.__attribute__.get(name).get('type', None)
if type == 'value':
- if valtype and valtype=='datetime' and val:
+ if valtype and valtype == 'datetime' and val:
if isinstance(val, list):
val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
else:
val = val.strftime('%Y-%m-%d %H:%M:%S')
- if valtype and valtype=='integer' and val:
- val = int(val)
return val
else:
return self.__attribute__.get(name, None)
def ical_reset(self, type):
+ """ Reset Calendar Attribute
+ @param self: The object pointer,
+ @param type: Get Attribute Type
+ """
for name in self.__attribute__:
if self.__attribute__[name]:
self.__attribute__[name][type] = None
return True
- def export_ical(self, cr, uid, datas, vobj=None, context={}):
- ical = vobject.iCalendar()
+ def format_date_tz(self, src_date, tz=None):
+ """ This function converts date into specifice timezone value
+ @param src_date: Date to be converted (datetime.datetime)
+ @return: Converted datetime.datetime object for the date
+ """
+ format = tools.DEFAULT_SERVER_DATETIME_FORMAT
+ date_str = src_date.strftime('%Y-%m-%d %H:%M:%S')
+ res_date = tools.server_to_local_timestamp(date_str, format, format, tz)
+ return datetime.strptime(res_date, "%Y-%m-%d %H:%M:%S")
+
+ def parse_ics(self, cr, uid, child, cal_children=None, context=None):
+ """ parse calendaring and scheduling information
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param context: A standard dictionary for contextual values """
+
+ att_data = []
+ exdates = []
+ _server_tzinfo = pytz.timezone(tools.get_server_timezone())
+
+ for cal_data in child.getChildren():
+ if cal_data.name.lower() == 'organizer':
+ dmail = { 'name': cal_data.params.get('CN', ['',])[0],
+ 'email': cal_data.value.lower().replace('mailto:',''),
+ # TODO: company?
+ }
+ self.ical_set(cal_data.name.lower(), mailto2str(dmail), 'value')
+ continue
+ if cal_data.name.lower() == 'attendee':
+ ctx = context.copy()
+ if cal_children:
+ ctx.update({'model': cal_children[cal_data.name.lower()]})
+ attendee = self.pool.get('basic.calendar.attendee')
+ att_data.append(attendee.import_cal(cr, uid, cal_data, context=ctx))
+ self.ical_set(cal_data.name.lower(), att_data, 'value')
+ continue
+ if cal_data.name.lower() == 'valarm':
+ alarm = self.pool.get('basic.calendar.alarm')
+ ctx = context.copy()
+ if cal_children:
+ ctx.update({'model': cal_children[cal_data.name.lower()]})
+ vals = alarm.import_cal(cr, uid, cal_data, context=ctx)
+ self.ical_set(cal_data.name.lower(), vals, 'value')
+ continue
+ if cal_data.name.lower() == 'exdate':
+ exdates += cal_data.value
+ exvals = []
+ for exdate in exdates:
+ exvals.append(datetime.fromtimestamp(time.mktime(exdate.utctimetuple())).strftime('%Y%m%dT%H%M%S'))
+ self.ical_set(cal_data.name.lower(), ','.join(exvals), 'value')
+ continue
+ if cal_data.name.lower() in self.__attribute__:
+ if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
+ self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
+ date_local = cal_data.value.astimezone(_server_tzinfo)
+ self.ical_set(cal_data.name.lower(), date_local, 'value')
+ continue
+ self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
+ vals = map_data(cr, uid, self, context=context)
+ return vals
+
+ def create_ics(self, cr, uid, datas, name, ical, context=None):
+ """ create calendaring and scheduling information
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param context: A standard dictionary for contextual values """
+
+ if not datas:
+ return
+ timezones = []
for data in datas:
- vevent = ical.add(vobj)
+ tzval = None
+ exfield = None
+ exdates = []
+ vevent = ical.add(name)
for field in self.__attribute__.keys():
map_field = self.ical_get(field, 'field')
map_type = self.ical_get(field, 'type')
if map_field in data.keys():
- if field == 'uid' :
+ if field == 'uid':
model = context.get('model', None)
if not model:
continue
- uidval = common.openobjectid2uid(cr, data[map_field], model)
+ uidval = openobjectid2uid(cr, data[map_field], model)
+ #Computation for getting events with the same UID (RFC4791 Section4.1)
+ #START
+ model_obj = self.pool.get(model)
+ r_ids = []
+ if model_obj._columns.get('recurrent_uid', None):
+ cr.execute('SELECT id FROM %s WHERE recurrent_uid=%%s' % model_obj._table,
+ (data[map_field],))
+ r_ids = map(lambda x: x[0], cr.fetchall())
+ if r_ids:
+ r_datas = model_obj.read(cr, uid, r_ids, context=context)
+ rcal = CalDAV.export_cal(self, cr, uid, r_datas, 'vevent', context=context)
+ for revents in rcal.contents.get('vevent', []):
+ ical.contents['vevent'].append(revents)
+ #END
+ if data.get('recurrent_uid', None):
+ # Change the UID value in case of modified event from any recurrent event
+ uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
vevent.add('uid').value = uidval
elif field == 'attendee' and data[map_field]:
+ model = self.__attribute__[field].get('object', False)
attendee_obj = self.pool.get('basic.calendar.attendee')
- vevent = attendee_obj.export_ical(cr, uid, data[map_field], vevent, context=context)
+ vevent = attendee_obj.export_cal(cr, uid, model, \
+ data[map_field], vevent, context=context)
elif field == 'valarm' and data[map_field]:
+ model = self.__attribute__[field].get('object', False)
+ ctx = context.copy()
+ ctx.update({'model': model})
alarm_obj = self.pool.get('basic.calendar.alarm')
- vevent = alarm_obj.export_ical(cr, uid, data[map_field][0], vevent, context=context)
+ vevent = alarm_obj.export_cal(cr, uid, model, \
+ data[map_field][0], vevent, context=ctx)
+ elif field == 'vtimezone' and data[map_field]:
+ tzval = data[map_field]
+ if tzval not in timezones:
+ tz_obj = self.pool.get('basic.calendar.timezone')
+ ical = tz_obj.export_cal(cr, uid, None, \
+ data[map_field], ical, context=context)
+ timezones.append(data[map_field])
+ if vevent.contents.get('recurrence-id'):
+ # Convert recurrence-id field value accroding to timezone value
+ recurid_val = vevent.contents.get('recurrence-id')[0].value
+ vevent.contents.get('recurrence-id')[0].params['TZID'] = [tzval.title()]
+ vevent.contents.get('recurrence-id')[0].value = self.format_date_tz(recurid_val, tzval.title())
+ if exfield:
+ # Set exdates according to timezone value
+ # This is the case when timezone mapping comes after the exdate mapping
+ # and we have exdate value available
+ exfield.params['TZID'] = [tzval.title()]
+ exdates_updated = []
+ for exdate in exdates:
+ exdates_updated.append(self.format_date_tz(parser.parse(exdate), tzval.title()))
+ exfield.value = exdates_updated
+ elif field == 'organizer' and data[map_field]:
+ organizer = str2mailto(data[map_field])
+ event_org = vevent.add('organizer')
+ event_org.params['CN'] = [organizer['name']]
+ event_org.value = 'MAILTO:' + (organizer.get('email') or '')
+ # TODO: company?
elif data[map_field]:
- if map_type == "text":
- vevent.add(field).value = str(data[map_field])
- elif map_type == 'datetime' and data[map_field]:
+ if map_type in ("char", "text"):
if field in ('exdate'):
- vevent.add(field).value = [parser.parse(data[map_field])]
+ exfield = vevent.add(field)
+ exdates = (data[map_field]).split(',')
+ if tzval:
+ # Set exdates according to timezone value
+ # This is the case when timezone mapping comes before the exdate mapping
+ # and we have timezone value available
+ exfield.params['TZID'] = [tzval.title()]
+ exdates_updated = []
+ for exdate in exdates:
+ exdates_updated.append(self.format_date_tz(parser.parse(exdate), tzval.title()))
+ exfield.value = exdates_updated
+ else:
+ vevent.add(field).value = 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])
elif map_type == "many2one":
- vevent.add(field).value = [data.get(map_field)[1]]
- 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
+ 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('basic.calendar.attendee')
- att_data.append(attendee.import_ical(cr, uid, cal_data))
- self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
- continue
- if cal_data.name.lower() == 'valarm':
- alarm = self.pool.get('basic.calendar.alarm')
- vals = alarm.import_ical(cr, uid, cal_data)
- self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
- continue
- if cal_data.name.lower() in self.__attribute__:
- self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
if child.name.lower() in ('vevent', 'vtodo'):
- vals = map_data(cr, uid, self)
+ vals = self.parse_ics(cr, uid, child, context=context)
else:
vals = {}
continue
self.ical_reset('value')
return res
-
-class Calendar(CalDAV, osv.osv_memory):
+class Calendar(CalDAV, osv.osv):
_name = 'basic.calendar'
+ _calname = 'calendar'
+
__attribute__ = {
'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
'vjournal': None, # Use: O-n, Type: Collection of Journal class
'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
-
}
+ _columns = {
+ 'name': fields.char("Name", size=64),
+ 'user_id': fields.many2one('res.users', 'Owner'),
+ 'collection_id': fields.many2one('document.directory', 'Collection', \
+ required=True),
+ 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
+ string="Type", size=64),
+ 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
+ 'create_date': fields.datetime('Created Date', readonly=True),
+ 'write_date': fields.datetime('Write Date', readonly=True),
+ 'description': fields.text("Description"),
+ 'calendar_color': fields.char('Color', size=20, help="For supporting clients, the color of the calendar entries"),
+ 'calendar_order': fields.integer('Order', help="For supporting clients, the order of this folder among the calendars"),
+ 'has_webcal': fields.boolean('WebCal', required=True, help="Also export a <name>.ics entry next to the calendar folder, with WebCal content."),
+ }
+
+ _defaults = {
+ 'has_webcal': False,
+ }
+
+ def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
+ if context is None:
+ context = {}
+ if not domain:
+ domain = []
+ res = []
+ ctx_res_id = context.get('res_id', None)
+ ctx_model = context.get('model', None)
+ for cal in self.browse(cr, uid, ids):
+ for line in cal.line_ids:
+ if ctx_model and ctx_model != line.object_id.model:
+ continue
+ if line.name in ('valarm', 'attendee'):
+ continue
+ line_domain = eval(line.domain or '[]', context)
+ line_domain += domain
+ if ctx_res_id:
+ line_domain += [('id','=',ctx_res_id)]
+ mod_obj = self.pool.get(line.object_id.model)
+ data_ids = mod_obj.search(cr, uid, line_domain, order="id", context=context)
+ for data in mod_obj.browse(cr, uid, data_ids, context):
+ ctx = parent and parent.context or None
+ if hasattr(data, 'recurrent_uid') and data.recurrent_uid:
+ # Skip for event which is child of other event
+ continue
+ node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
+ res.append(node)
+ return res
+
+
+ def get_cal_max_modified(self, cr, uid, ids, parent=None, domain=None, context=None):
+ if context is None:
+ context = {}
+ if not domain:
+ domain = []
+ res = None
+ ctx_res_id = context.get('res_id', None)
+ ctx_model = context.get('model', None)
+ for cal in self.browse(cr, uid, ids):
+ for line in cal.line_ids:
+ if ctx_model and ctx_model != line.object_id.model:
+ continue
+ if line.name in ('valarm', 'attendee'):
+ continue
+ line_domain = eval(line.domain or '[]', context)
+ line_domain += domain
+ if ctx_res_id:
+ line_domain += [('id','=',ctx_res_id)]
+ mod_obj = self.pool.get(line.object_id.model)
+ max_data = get_last_modified(mod_obj, cr, uid, line_domain, context=context)
+ if res and res > max_data:
+ continue
+ res = max_data
+ return res
+
+ def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
+ """ Export Calendar
+ @param ids: List of calendar’s IDs
+ @param vobj: the type of object to export
+ @return the ical data.
+ """
+ if context is None:
+ context = {}
+ ctx_model = context.get('model', None)
+ ctx_res_id = context.get('res_id', None)
+ ical = vobject.iCalendar()
+ for cal in self.browse(cr, uid, ids, context=context):
+ for line in cal.line_ids:
+ if ctx_model and ctx_model != line.object_id.model:
+ continue
+ if line.name in ('valarm', 'attendee'):
+ continue
+ domain = eval(line.domain or '[]', context)
+ if ctx_res_id:
+ domain += [('id','=',ctx_res_id)]
+ mod_obj = self.pool.get(line.object_id.model)
+ data_ids = mod_obj.search(cr, uid, domain, context=context)
+ datas = mod_obj.read(cr, uid, data_ids, context=context)
+ context.update({'model': line.object_id.model,
+ 'calendar_id': cal.id
+ })
+ self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
+ self.create_ics(cr, uid, datas, line.name, ical, context=context)
+ return ical.serialize()
+
+ def import_cal(self, cr, uid, content, data_id=None, context=None):
+ """ Import Calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param data_id: Get Data’s ID or False
+ @param context: A standard dictionary for contextual values
+ """
+ if context is None:
+ context = {}
+ vals = []
+ ical_data = content
+ parsedCal = vobject.readOne(ical_data)
+ if not data_id:
+ data_id = self.search(cr, uid, [])[0]
+ cal = self.browse(cr, uid, data_id, context=context)
+ cal_children = {}
+
+ for line in cal.line_ids:
+ cal_children[line.name] = line.object_id.model
+ objs = []
+ checked = True
+ for child in parsedCal.getChildren():
+ if child.name.lower() in cal_children:
+ context.update({'model': cal_children[child.name.lower()],
+ 'calendar_id': cal['id']
+ })
+ self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
+ val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
+ vals.append(val)
+ objs.append(cal_children[child.name.lower()])
+ elif child.name.upper() == 'CALSCALE':
+ if child.value.upper() != 'GREGORIAN':
+ self._logger.warning('How do I handle %s calendars?',child.value)
+ elif child.name.upper() in ('PRODID', 'VERSION'):
+ pass
+ elif child.name.upper().startswith('X-'):
+ self._logger.debug("skipping custom node %s", child.name)
+ else:
+ self._logger.debug("skipping node %s", child.name)
+
+ res = []
+ for obj_name in list(set(objs)):
+ obj = self.pool.get(obj_name)
+ if hasattr(obj, 'check_import'):
+ r = obj.check_import(cr, uid, vals, context=context)
+ checked = True
+ res.extend(r)
+
+ if not checked:
+ r = self.check_import(cr, uid, vals, context=context)
+ res.extend(r)
+ return res
Calendar()
+
+class basic_calendar_line(osv.osv):
+ """ Calendar Lines """
+
+ _name = 'basic.calendar.lines'
+ _description = 'Calendar Lines'
+
+ _columns = {
+ 'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
+ ('valarm', 'Alarm'), \
+ ('attendee', 'Attendee')], \
+ string="Type", size=64),
+ 'object_id': fields.many2one('ir.model', 'Object'),
+ 'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
+ required=True, ondelete='cascade'),
+ 'domain': fields.char('Domain', size=124),
+ 'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
+ }
+
+ _defaults = {
+ 'domain': lambda *a: '[]',
+ }
+
+ def create(self, cr, uid, vals, context=None):
+ """ create calendar's line
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param vals: Get the Values
+ @param context: A standard dictionary for contextual values
+ """
+
+ cr.execute("SELECT COUNT(id) FROM basic_calendar_lines \
+ WHERE name=%s AND calendar_id=%s",
+ (vals.get('name'), vals.get('calendar_id')))
+ res = cr.fetchone()
+ if res:
+ if res[0] > 0:
+ raise osv.except_osv(_('Warning !'), _('Can not create line "%s" more than once') % (vals.get('name')))
+ return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
+
+basic_calendar_line()
+
+class basic_calendar_alias(osv.osv):
+ """ Mapping of client filenames to ORM ids of calendar records
+
+ Since some clients insist on putting arbitrary filenames on the .ics data
+ they send us, and they won't respect the redirection "Location:" header,
+ we have to store those filenames and allow clients to call our calendar
+ records with them.
+ Note that adding a column to all tables that would possibly hold calendar-
+ mapped data won't work. The user is always allowed to specify more
+ calendars, on any arbitrary ORM object, without need to alter those tables'
+ data or structure
+ """
+ _name = 'basic.calendar.alias'
+ _columns = {
+ 'name': fields.char('Filename', size=512, required=True, select=1),
+ 'cal_line_id': fields.many2one('basic.calendar.lines', 'Calendar', required=True,
+ select=1, help='The calendar/line this mapping applies to'),
+ 'res_id': fields.integer('Res. ID', required=True, select=1),
+ }
+
+ _sql_constraints = [ ('name_cal_uniq', 'UNIQUE(cal_line_id, name)',
+ _('The same filename cannot apply to two records!')), ]
+
+basic_calendar_alias()
+
+class basic_calendar_attribute(osv.osv):
+ _name = 'basic.calendar.attributes'
+ _description = 'Calendar attributes'
+ _columns = {
+ 'name': fields.char("Name", size=64, required=True),
+ 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
+ ('alarm', 'Alarm'), \
+ ('attendee', 'Attendee')], \
+ string="Type", size=64, required=True),
+ }
+
+basic_calendar_attribute()
+
+
+class basic_calendar_fields(osv.osv):
+ """ Calendar fields """
+
+ _name = 'basic.calendar.fields'
+ _description = 'Calendar fields'
+ _order = 'name'
+
+ _columns = {
+ 'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
+ 'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
+ 'type_id': fields.many2one('basic.calendar.lines', 'Type', \
+ required=True, ondelete='cascade'),
+ 'expr': fields.char("Expression", size=64),
+ 'fn': fields.selection([('field', 'Use the field'),
+ ('const', 'Expression as constant'),
+ ('hours', 'Interval in hours'),
+ ('datetime_utc', 'Datetime In UTC'),
+ ], 'Function'),
+ 'mapping': fields.text('Mapping'),
+ }
+
+ _defaults = {
+ 'fn': 'field',
+ }
+
+ _sql_constraints = [
+ ( 'name_type_uniq', 'UNIQUE(name, type_id)', 'Can not map a field more than once'),
+ ]
+
+ def check_line(self, cr, uid, vals, name, context=None):
+ """ check calendar's line
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param vals: Get Values
+ @param context: A standard dictionary for contextual values
+ """
+ f_obj = self.pool.get('ir.model.fields')
+ field = f_obj.browse(cr, uid, vals['field_id'], context=context)
+ relation = field.relation
+ line_obj = self.pool.get('basic.calendar.lines')
+ l_id = line_obj.search(cr, uid, [('name', '=', name)])
+ if l_id:
+ line = line_obj.browse(cr, uid, l_id, context=context)[0]
+ line_rel = line.object_id.model
+ if (relation != 'NULL') and (not relation == line_rel):
+ raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines') % (name))
+ return True
+
+ def create(self, cr, uid, vals, context=None):
+ """ Create Calendar's fields
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param vals: Get Values
+ @param context: A standard dictionary for contextual values
+ """
+
+ cr.execute('SELECT name FROM basic_calendar_attributes \
+ WHERE id=%s', (vals.get('name'),))
+ name = cr.fetchone()
+ name = name[0]
+ if name in ('valarm', 'attendee'):
+ self.check_line(cr, uid, vals, name, context=context)
+ return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
+
+ def write(self, cr, uid, ids, vals, context=None):
+ """ write Calendar's fields
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param vals: Get Values
+ @param context: A standard dictionary for contextual values
+ """
+
+ if not vals:
+ return
+ for id in ids:
+ field = self.browse(cr, uid, id, context=context)
+ name = field.name.name
+ if name in ('valarm', 'attendee'):
+ self.check_line(cr, uid, vals, name, context=context)
+ return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
+
+basic_calendar_fields()
+
+
class Event(CalDAV, osv.osv_memory):
_name = 'basic.calendar.event'
+ _calname = 'vevent'
__attribute__ = {
'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
'created': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that the calendar information was created by the calendar user agent in the calendar store.
'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.
'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a reference to contact information associated with the calendar component.
'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
- 'rstatus': None,
+ 'rstatus': None,
'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
# like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
'resources': None, # Use: O-n, Type: TEXT, Defines the equipment or resources anticipated for an activity specified by a calendar entity like RESOURCES:EASEL,PROJECTOR,VCR, LANGUAGE=fr:1 raton-laveur
'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
- 'x-prop': None,
+ 'x-prop': None,
'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
}
- def export_ical(self, cr, uid, datas, context={}):
- return super(Event, self).export_ical(cr, uid, datas, 'vevent', context=context)
+
+ def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
+ """ Export calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param datas: Get datas
+ @param context: A standard dictionary for contextual values
+ """
+
+ return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
Event()
+
class ToDo(CalDAV, osv.osv_memory):
_name = 'basic.calendar.todo'
+ _calname = 'vtodo'
__attribute__ = {
- 'class': None,
- 'completed': None,
- 'created': None,
- 'description': None,
- 'dtstamp': None,
- 'dtstart': None,
- '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,
+ '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_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.
'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,
'x-prop': None, # Use: O-n, Type: Text,
}
+ def get_name_offset(self, cr, uid, tzid, context=None):
+ """ Get Name Offset value
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param context: A standard dictionary for contextual values
+ """
+
+ mytz = pytz.timezone(tzid.title())
+ mydt = datetime.now(tz=mytz)
+ offset = mydt.utcoffset()
+ val = offset.days * 24 + float(offset.seconds) / 3600
+ realoffset = '%02d%02d' % (math.floor(abs(val)), \
+ round(abs(val) % 1 + 0.01, 2) * 60)
+ realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
+ return (mydt.tzname(), realoffset)
+
+ def export_cal(self, cr, uid, model, tzid, ical, context=None):
+ """ Export Calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param model: Get Model's name
+ @param context: A standard dictionary for contextual values
+ """
+ if context is None:
+ context = {}
+ ctx = context.copy()
+ ctx.update({'model': model})
+ cal_tz = ical.add('vtimezone')
+ cal_tz.add('TZID').value = tzid.title()
+ tz_std = cal_tz.add('STANDARD')
+ tzname, offset = self.get_name_offset(cr, uid, tzid)
+ tz_std.add("TZOFFSETFROM").value = offset
+ tz_std.add("TZOFFSETTO").value = offset
+ #TODO: Get start date for timezone
+ tz_std.add("DTSTART").value = datetime.strptime('1970-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')
+ tz_std.add("TZNAME").value = tzname
+ return ical
+
+ def import_cal(self, cr, uid, ical_data, context=None):
+ """ Import Calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param ical_data: Get calendar's data
+ @param context: A standard dictionary for contextual values
+ """
+
+ for child in ical_data.getChildren():
+ if child.name.lower() == 'tzid':
+ tzname = child.value
+ self.ical_set(child.name.lower(), tzname, 'value')
+ vals = map_data(cr, uid, self, context=context)
+ return vals
+
+Timezone()
+
class Alarm(CalDAV, osv.osv_memory):
_name = 'basic.calendar.alarm'
+ _calname = 'alarm'
+
__attribute__ = {
'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
'description': None, # Type: Text, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property. Use:- R-1 for DISPLAY,Use:- R-1 for EMAIL,Use:- R-1 for PROCEDURE
'duration': None, # Type: DURATION, Duration' and 'repeat' are both optional, and MUST NOT occur more than once each, but if one occurs, so MUST the other. Use:- 0-1 for AUDIO, EMAIL and PROCEDURE, Use:- 0-n for DISPLAY
'repeat': None, # Type: INTEGER, Duration' and 'repeat' are both optional, and MUST NOT occur more than once each, but if one occurs, so MUST the other. Use:- 0-1 for AUDIO, EMAIL and PROCEDURE, Use:- 0-n for DISPLAY
'attach': None, # Use:- O-n: which MUST point to a sound resource, which is rendered when the alarm is triggered for AUDIO, Use:- O-n: which are intended to be sent as message attachments for EMAIL, Use:- R-1:which MUST point to a procedure resource, which is invoked when the alarm is triggered for PROCEDURE.
- 'x-prop': None,
+ 'x-prop': None,
}
- def export_ical(self, cr, uid, alarm_id, vevent, context={}):
+ def export_cal(self, cr, uid, model, alarm_id, vevent, context=None):
+ """ Export Calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param model: Get Model's name
+ @param alarm_id: Get Alarm's Id
+ @param context: A standard dictionary for contextual values
+ """
valarm = vevent.add('valarm')
- alarm_object = self.pool.get('calendar.alarm')
+ alarm_object = self.pool.get(model)
alarm_data = alarm_object.read(cr, uid, alarm_id, [])
# Compute trigger data
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'
+ related = days >= 0 and 'after' or 'before'
else:
duration = abs(diff / 3600)
interval = 'hours'
- related = days>=0 and 'after' or 'before'
+ related = days >= 0 and 'after' or 'before'
self.ical_set('trigger_interval', interval, 'value')
self.ical_set('trigger_duration', duration, 'value')
self.ical_set('trigger_occurs', related.lower(), 'value')
self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
else:
self.ical_set(child.name.lower(), child.value.lower(), 'value')
- vals = map_data(cr, uid, self)
+ vals = map_data(cr, uid, self, context=context)
return vals
Alarm()
+
class Attendee(CalDAV, osv.osv_memory):
_name = 'basic.calendar.attendee'
+ _calname = 'attendee'
+
__attribute__ = {
'cutype': None, # Use: 0-1 Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
'member': None, # Use: 0-1 Specify the group or list membership of the calendar user specified by the property.
'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]+':'+ \
self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
if not ical_data.params.get('CN'):
self.ical_set('cn', ical_data.value, 'value')
- vals = map_data(cr, uid, self)
+ vals = map_data(cr, uid, self, context=context)
return vals
- def export_ical(self, cr, uid, attendee_id, vevent, context={}):
- attendee_object = self.pool.get('calendar.attendee')
- for attendee in attendee_object.read(cr, uid, attendee_id, []):
+ def export_cal(self, cr, uid, model, attendee_ids, vevent, context=None):
+ """ Export Calendar
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param model: Get model's name
+ @param attendee_ids: Get Attendee's Id
+ @param context: A standard dictionary for contextual values
+ """
+ if context is None:
+ context = {}
+ attendee_object = self.pool.get(model)
+ ctx = context.copy()
+ ctx.update({'model': model})
+ self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
+ for attendee in attendee_object.read(cr, uid, attendee_ids, []):
attendee_add = vevent.add('attendee')
- for a_key, a_val in attendee_object.__attribute__.items():
- if attendee[a_val['field']]:
- if a_val['type'] == 'text':
+ cn_val = ''
+ for a_key, a_val in self.__attribute__.items():
+ if attendee[a_val['field']] and a_val['field'] != 'cn':
+ if a_val['type'] in ('text', 'char', 'selection'):
attendee_add.params[a_key] = [str(attendee[a_val['field']])]
elif a_val['type'] == 'boolean':
attendee_add.params[a_key] = [str(attendee[a_val['field']])]
+ if a_val['field'] == 'cn' and attendee[a_val['field']]:
+ cn_val = [str(attendee[a_val['field']])]
+ if cn_val:
+ attendee_add.params['CN'] = cn_val
+ if not attendee['email']:
+ attendee_add.value = 'MAILTO:'
+ #raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
+ elif attendee['email']:
+ attendee_add.value = 'MAILTO:' + attendee['email']
return vevent
Attendee()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: