from dateutil.rrule import *
from osv import osv, fields
from tools.translate import _
-import base64
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/')
+ 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
@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)
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
+ qry = 'SELECT DISTINCT(id) FROM %s' % model_obj._table
if rdate:
- qry += " where recurrent_id='%s'" % (rdate)
- cr.execute(qry)
+ 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 (False, None)
def openobjectid2uid(cr, uidval, oomodel):
- """ Open Object Id To UId
+ """ Gives the value of UID for VEVENT
@param cr: the current row, from the database cursor,
- @param uidval: Get USerId vale
+ @param uidval: Id value of the Event
@oomodel: Open Object ModelName """
value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
return value
-def get_attribute_mapping(cr, uid, calname, context={}):
+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 not context:
+ if context is None:
context = {}
pool = pooler.get_pool(cr.dbname)
field_obj = pool.get('basic.calendar.fields')
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):
+ 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'):
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[map_val.lower()]
+ map_val = mapping.get(map_val.lower(), False)
else:
map_val = map_val.lower()
if field_type == 'many2many':
class CalDAV(object):
__attribute__ = {}
-
-
+ _logger = logging.getLogger('document.caldav')
def ical_set(self, name, value, type):
""" set calendar Attribute
@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)
@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 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:
self.ical_set(cal_data.name.lower(), vals, 'value')
continue
if cal_data.name.lower() == 'exdate':
- exval = map(lambda x: str(x), cal_data.value)
- self.ical_set(cal_data.name.lower(), ','.join(exval), 'value')
+ 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
if not datas:
return
timezones = []
- for data in datas:
+ 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')
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]))
+ 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:
- rcal = self.export_cal(cr, uid, r_ids, 'vevent', context=context)
+ 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]:
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] and data[map_field] not in timezones:
+ elif field == 'vtimezone' and data[map_field]:
tzval = data[map_field]
- 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 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'):
- vevent.add(field).value = map(parser.parse, (data[map_field]).split(','))
+ 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)
- dtfield.value = parser.parse(data[map_field])
if tzval:
+ # Export the date according to the event timezone value
dtfield.params['TZID'] = [tzval.title()]
+ dtfield.value = self.format_date_tz(parser.parse(data[map_field]), tzval.title())
+ else:
+ dtfield.value = parser.parse(data[map_field])
+
+ elif map_type == 'utc'and data[map_field]:
+ if tzval:
+ local = pytz.timezone (tzval.title())
+ naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+ local_dt = naive.replace (tzinfo = local)
+ utc_dt = local_dt.astimezone (pytz.utc)
+ vevent.add(field).value = utc_dt
+ else:
+ utc_timezone = pytz.timezone ('UTC')
+ naive = datetime.strptime (data[map_field], "%Y-%m-%d %H:%M:%S")
+ local_dt = naive.replace (tzinfo = utc_timezone)
+ utc_dt = local_dt.astimezone (pytz.utc)
+ vevent.add(field).value = utc_dt
+
elif map_type == "timedelta":
vevent.add(field).value = timedelta(hours=data[map_field])
elif map_type == "many2one":
else:
for key1, val1 in self.ical_get(field, 'mapping').items():
if val1 == data[map_field]:
- vevent.add(field).value = key1
+ vevent.add(field).value = key1.upper()
return vevent
- def check_import(self, cr, uid, vals, context={}):
+ def check_import(self, cr, uid, vals, context=None):
"""
@param self: The object pointer
@param cr: the current row, from the database cursor,
@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:
- exists, r_id = uid2openobjectid(cr, val['id'], context.get('model'), \
+ # 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')
+ if val.has_key('create_date'):
+ val.pop('create_date')
u_id = val.get('id', None)
val.pop('id')
if exists and r_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, e:
- raise osv.except_osv(('Error !'), (str(e)))
+ except Exception:
+ raise
return ids
- def export_cal(self, cr, uid, datas, vobj=None, context={}):
+ 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 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 Exception, e:
- raise osv.except_osv(('Error !'), (str(e)))
+ except:
+ raise # osv.except_osv(('Error !'), (str(e)))
def import_cal(self, cr, uid, content, data_id=None, context=None):
""" Import Calendar
@param context: A standard dictionary for contextual values
"""
- ical_data = base64.decodestring(content)
+ ical_data = content
self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
parsedCal = vobject.readOne(ical_data)
res = []
return res
class Calendar(CalDAV, osv.osv):
- _name = 'basic.calendar'
+ _name = 'basic.calendar'
_calname = 'calendar'
__attribute__ = {
'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
}
_columns = {
- 'name': fields.char("Name", size=64),
+ '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'),
- 'write_date': fields.datetime('Modifided Date'),
+ '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 not context:
+ if context is None:
context = {}
if not domain:
domain = []
res = []
- ctx_res_id = context.get('res_id', None)
- ctx_model = context.get('model', 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)
+ line_domain = eval(line.domain or '[]', context)
line_domain += domain
- if ctx_res_id:
+ 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, context=context)
+ 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
- node = res_node_calendar('%s' %data.id, parent, ctx, data, line.object_id.model, data.id)
+ 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 self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
@param ids: List of calendar’s IDs
- @param context: A standard dictionary for contextual values
+ @param vobj: the type of object to export
+ @return the ical data.
"""
- if not context:
+ if context is None:
context = {}
- ctx_model = context.get('model', None)
- ctx_res_id = context.get('res_id', None)
+ 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):
+ 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)
- if ctx_res_id:
+ 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)
context.update({'model': line.object_id.model,
'calendar_id': cal.id
})
- self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
+ self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
self.create_ics(cr, uid, datas, line.name, ical, context=context)
return ical.serialize()
@param data_id: Get Data’s ID or False
@param context: A standard dictionary for contextual values
"""
-
- if not context:
+ if context is None:
context = {}
- vals = []
- ical_data = base64.decodestring(content)
+ vals = []
+ ical_data = content
parsedCal = vobject.readOne(ical_data)
if not data_id:
- data_id = self.search(cr, uid, [])[0]
+ data_id = self.search(cr, uid, [])[0]
cal = self.browse(cr, uid, data_id, context=context)
cal_children = {}
- count = 0
+
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()],
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)
- obj = self.pool.get(cal_children[child.name.lower()])
- if hasattr(obj, 'check_import'):
- obj.check_import(cr, uid, vals, context=context)
- else:
- self.check_import(cr, uid, vals, context=context)
- return {}
+ 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()
'domain': lambda *a: '[]',
}
- def create(self, cr, uid, vals, context={}):
+ 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 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')))
+ 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'))))
+ 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'
_name = 'basic.calendar.fields'
_description = 'Calendar fields'
+ _order = 'name'
_columns = {
'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
'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': lambda *a: 'field',
+ '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
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)))
+ raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines') % (name))
return True
- def create(self, cr, uid, vals, context={}):
+ 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 context: A standard dictionary for contextual values
"""
- cr.execute('select name from basic_calendar_attributes \
- where id=%s' % (vals.get('name')))
+ 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)
- cr.execute("Select count(id) from basic_calendar_fields \
- where name=%s and type_id=%s" % (vals.get('name'), vals.get('type_id')))
- res = cr.fetchone()
- if res:
- if res[0] > 0:
- raise osv.except_osv(_('Warning !'), _('Can not map the field more than once'))
return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
name = field.name.name
if name in ('valarm', 'attendee'):
self.check_line(cr, uid, vals, name, context=context)
- qry = "Select count(id) from basic_calendar_fields \
- where name=%s and type_id=%s" % (field.name.id, field.type_id.id)
- cr.execute(qry)
- res = cr.fetchone()
- if res:
- if res[0] > 1:
- raise osv.except_osv(_('Warning !'), _('Can not map same field more than once'))
return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
basic_calendar_fields()
'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
}
- def export_cal(self, cr, uid, datas, vobj='vevent', 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,
'rrule': None,
}
- def export_cal(self, cr, uid, datas, vobj='vevent', 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,
'x-prop': None, # Use: O-n, Type: Text,
}
- def get_name_offset(self, cr, uid, tzid, context={}):
+ 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,
realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
return (mydt.tzname(), realoffset)
- def export_cal(self, cr, uid, model, tzid, ical, context={}):
+ 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 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')
tzname, offset = self.get_name_offset(cr, uid, tzid)
tz_std.add("TZOFFSETFROM").value = offset
tz_std.add("TZOFFSETTO").value = offset
- tz_std.add("DTSTART").value = datetime.now() # TODO
+ #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
'x-prop': None,
}
- def export_cal(self, cr, uid, model, 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 alarm_id: Get Alarm's Id
@param context: A standard dictionary for contextual values
"""
-
valarm = vevent.add('valarm')
alarm_object = self.pool.get(model)
alarm_data = alarm_object.read(cr, uid, alarm_id, [])
@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'
@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)
vals = map_data(cr, uid, self, context=context)
return vals
- def export_cal(self, cr, uid, model, attendee_ids, vevent, context={}):
+ 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 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})
if cn_val:
attendee_add.params['CN'] = cn_val
if not attendee['email']:
- raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
+ 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