1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from datetime import datetime, timedelta
23 from dateutil import parser
24 from dateutil.rrule import *
25 from osv import osv, fields
26 from tools.translate import _
33 from caldav_node import res_node_calendar
38 raise osv.except_osv('vobject Import Error!','Please install python-vobject \
39 from http://vobject.skyhouseconsulting.com/')
41 # O-1 Optional and can come only once
42 # O-n Optional and can come more than once
43 # R-1 Required and can come only once
44 # R-n Required and can come more than once
46 def uid2openobjectid(cr, uidval, oomodel, rdate):
47 """ UID To Open Object Id
48 @param cr: the current row, from the database cursor,
49 @param uidval: Get USerId vale
50 @oomodel: Open Object ModelName
51 @param rdate: Get Recurrent Date
53 __rege = re.compile(r'OpenObject-([\w|\.]+)_([0-9]+)@(\w+)$')
56 wematch = __rege.match(uidval.encode('utf8'))
60 model, id, dbname = wematch.groups()
61 model_obj = pooler.get_pool(cr.dbname).get(model)
62 if (not model == oomodel) or (not dbname == cr.dbname):
64 qry = 'select distinct(id) from %s' % model_obj._table
66 qry += " where recurrent_id='%s'" % (rdate)
72 ids = map(lambda x: str(x[0]), cr.fetchall())
77 def openobjectid2uid(cr, uidval, oomodel):
78 """ Open Object Id To UId
79 @param cr: the current row, from the database cursor,
80 @param uidval: Get USerId vale
81 @oomodel: Open Object ModelName """
83 value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
86 def get_attribute_mapping(cr, uid, calname, context=None):
87 """ Attribute Mapping with Basic calendar fields and lines
88 @param cr: the current row, from the database cursor,
89 @param uid: the current user’s ID for security checks,
90 @param calname: Get Calendar name
91 @param context: A standard dictionary for contextual values """
95 pool = pooler.get_pool(cr.dbname)
96 field_obj = pool.get('basic.calendar.fields')
97 type_obj = pool.get('basic.calendar.lines')
98 domain = [('object_id.model', '=', context.get('model'))]
99 if context.get('calendar_id'):
100 domain.append(('calendar_id', '=', context.get('calendar_id')))
101 type_id = type_obj.search(cr, uid, domain)
102 fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])])
104 for field in field_obj.browse(cr, uid, fids):
105 attr = field.name.name
107 res[attr]['field'] = field.field_id.name
108 res[attr]['type'] = field.field_id.ttype
109 if field.fn == 'hours':
110 res[attr]['type'] = "timedelta"
111 if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
112 res[attr]['object'] = field.field_id.relation
113 elif res[attr]['type'] in ('selection') and field.mapping:
114 res[attr]['mapping'] = eval(field.mapping)
115 if not res.get('uid', None):
117 res['uid']['field'] = 'id'
118 res['uid']['type'] = "integer"
121 def map_data(cr, uid, obj, context=None):
123 @param self: The object pointer
124 @param cr: the current row, from the database cursor,"""
127 for map_dict in obj.__attribute__:
128 map_val = obj.ical_get(map_dict, 'value')
129 field = obj.ical_get(map_dict, 'field')
130 field_type = obj.ical_get(map_dict, 'type')
132 if field_type == 'selection':
135 mapping = obj.__attribute__[map_dict].get('mapping', False)
137 map_val = mapping.get(map_val.lower(), False)
139 map_val = map_val.lower()
140 if field_type == 'many2many':
145 model = obj.__attribute__[map_dict].get('object', False)
146 modobj = obj.pool.get(model)
147 for map_vall in map_val:
148 id = modobj.create(cr, uid, map_vall, context=context)
150 vals[field] = [(6, 0, ids)]
152 if field_type == 'many2one':
154 if not map_val or not isinstance(map_val, dict):
157 model = obj.__attribute__[map_dict].get('object', False)
158 modobj = obj.pool.get(model)
159 # check if the record exists or not
160 key1 = map_val.keys()
161 value1 = map_val.values()
162 domain = [(key1[i], '=', value1[i]) for i in range(len(key1)) if value1[i]]
163 exist_id = modobj.search(cr, uid, domain, context=context)
167 id = modobj.create(cr, uid, map_val, context=context)
170 if field_type == 'timedelta':
172 vals[field] = (map_val.seconds/float(86400) + map_val.days)
173 vals[field] = map_val
176 class CalDAV(object):
181 def ical_set(self, name, value, type):
182 """ set calendar Attribute
183 @param self: The object pointer,
184 @param name: Get Attribute Name
185 @param value: Get Attribute Value
186 @param type: Get Attribute Type
188 if name in self.__attribute__ and self.__attribute__[name]:
189 self.__attribute__[name][type] = value
192 def ical_get(self, name, type):
193 """ Get calendar Attribute
194 @param self: The object pointer,
195 @param name: Get Attribute Name
196 @param type: Get Attribute Type
199 if self.__attribute__.get(name):
200 val = self.__attribute__.get(name).get(type, None)
201 valtype = self.__attribute__.get(name).get('type', None)
203 if valtype and valtype == 'datetime' and val:
204 if isinstance(val, list):
205 val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
207 val = val.strftime('%Y-%m-%d %H:%M:%S')
210 return self.__attribute__.get(name, None)
212 def ical_reset(self, type):
213 """ Reset Calendar Attribute
214 @param self: The object pointer,
215 @param type: Get Attribute Type
218 for name in self.__attribute__:
219 if self.__attribute__[name]:
220 self.__attribute__[name][type] = None
223 def format_date_tz(self, date, tz=None):
224 format = tools.DEFAULT_SERVER_DATETIME_FORMAT
225 return tools.server_to_local_timestamp(date, format, format, tz)
227 def parse_ics(self, cr, uid, child, cal_children=None, context=None):
228 """ parse calendaring and scheduling information
229 @param self: The object pointer
230 @param cr: the current row, from the database cursor,
231 @param uid: the current user’s ID for security checks,
232 @param context: A standard dictionary for contextual values """
236 _server_tzinfo = pytz.timezone(tools.get_server_timezone())
238 for cal_data in child.getChildren():
239 if cal_data.name.lower() == 'organizer':
240 self.ical_set(cal_data.name.lower(),
241 {'name': cal_data.params.get('CN', ['',])[0]},
244 if cal_data.name.lower() == 'attendee':
247 ctx.update({'model': cal_children[cal_data.name.lower()]})
248 attendee = self.pool.get('basic.calendar.attendee')
249 att_data.append(attendee.import_cal(cr, uid, cal_data, context=ctx))
250 self.ical_set(cal_data.name.lower(), att_data, 'value')
252 if cal_data.name.lower() == 'valarm':
253 alarm = self.pool.get('basic.calendar.alarm')
256 ctx.update({'model': cal_children[cal_data.name.lower()]})
257 vals = alarm.import_cal(cr, uid, cal_data, context=ctx)
258 self.ical_set(cal_data.name.lower(), vals, 'value')
260 if cal_data.name.lower() == 'exdate':
261 exdates += cal_data.value
263 for exdate in exdates:
264 exvals.append(datetime.fromtimestamp(time.mktime(exdate.utctimetuple())).strftime('%Y%m%dT%H%M%S'))
265 self.ical_set(cal_data.name.lower(), ','.join(exvals), 'value')
267 if cal_data.name.lower() in self.__attribute__:
268 if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
269 # since we do convert, do we also need to save the original tzid?
270 # self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
272 date_local = cal_data.value.astimezone(_server_tzinfo)
273 self.ical_set(cal_data.name.lower(), date_local, 'value')
275 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
276 vals = map_data(cr, uid, self, context=context)
279 def create_ics(self, cr, uid, datas, name, ical, context=None):
280 """ create calendaring and scheduling information
281 @param self: The object pointer
282 @param cr: the current row, from the database cursor,
283 @param uid: the current user’s ID for security checks,
284 @param context: A standard dictionary for contextual values """
293 vevent = ical.add(name)
294 for field in self.__attribute__.keys():
295 map_field = self.ical_get(field, 'field')
296 map_type = self.ical_get(field, 'type')
297 if map_field in data.keys():
299 model = context.get('model', None)
302 uidval = openobjectid2uid(cr, data[map_field], model)
303 model_obj = self.pool.get(model)
305 if model_obj._columns.get('recurrent_uid', None):
306 cr.execute('select id from %s where recurrent_uid=%s'
307 % (model_obj._table, data[map_field]))
308 r_ids = map(lambda x: x[0], cr.fetchall())
310 rcal = self.export_cal(cr, uid, r_ids, 'vevent', context=context)
311 if data.get('recurrent_uid', None):
312 uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
313 vevent.add('uid').value = uidval
314 elif field == 'attendee' and data[map_field]:
315 model = self.__attribute__[field].get('object', False)
316 attendee_obj = self.pool.get('basic.calendar.attendee')
317 vevent = attendee_obj.export_cal(cr, uid, model, \
318 data[map_field], vevent, context=context)
319 elif field == 'valarm' and data[map_field]:
320 model = self.__attribute__[field].get('object', False)
322 ctx.update({'model': model})
323 alarm_obj = self.pool.get('basic.calendar.alarm')
324 vevent = alarm_obj.export_cal(cr, uid, model, \
325 data[map_field][0], vevent, context=ctx)
326 elif field == 'vtimezone' and data[map_field]:
327 tzval = data[map_field]
328 if tzval not in timezones:
329 tz_obj = self.pool.get('basic.calendar.timezone')
330 ical = tz_obj.export_cal(cr, uid, None, \
331 data[map_field], ical, context=context)
332 timezones.append(data[map_field])
334 exfield.params['TZID'] = [tzval.title()]
336 for exdate in exdates:
337 date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
338 dest_date = self.format_date_tz(date1, tzval.title())
339 ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
340 exdates_updated.append(ex_date)
341 exfield.value = map(parser.parse, exdates_updated)
342 elif field == 'organizer' and data[map_field]:
343 organizer = data[map_field]
344 event_org = vevent.add('organizer')
345 event_org.params['CN'] = [organizer]
346 event_org.value = 'MAILTO:' + (organizer)
347 elif data[map_field]:
348 if map_type in ("char", "text"):
349 if field in ('exdate'):
350 exfield = vevent.add(field)
351 exdates = (data[map_field]).split(',')
353 exfield.params['TZID'] = [tzval.title()]
355 for exdate in exdates:
356 date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
357 dest_date = self.format_date_tz(date1, tzval.title())
358 ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
359 exdates_updated.append(ex_date)
360 exdates = exdates_updated
361 exfield.value = map(parser.parse, exdates)
363 vevent.add(field).value = tools.ustr(data[map_field])
364 elif map_type in ('datetime', 'date') and data[map_field]:
365 dtfield = vevent.add(field)
367 dest_date = self.format_date_tz(data[map_field], tzval.title())
368 dtfield.params['TZID'] = [tzval.title()]
369 dtfield.value = parser.parse(dest_date)
371 dtfield.value = parser.parse(data[map_field])
372 elif map_type == "timedelta":
373 vevent.add(field).value = timedelta(hours=data[map_field])
374 elif map_type == "many2one":
375 vevent.add(field).value = tools.ustr(data.get(map_field)[1])
376 elif map_type in ("float", "integer"):
377 vevent.add(field).value = str(data.get(map_field))
378 elif map_type == "selection":
379 if not self.ical_get(field, 'mapping'):
380 vevent.add(field).value = (tools.ustr(data[map_field])).upper()
382 for key1, val1 in self.ical_get(field, 'mapping').items():
383 if val1 == data[map_field]:
384 vevent.add(field).value = key1
387 def check_import(self, cr, uid, vals, context=None):
389 @param self: The object pointer
390 @param cr: the current row, from the database cursor,
391 @param uid: the current user’s ID for security checks,
392 @param vals: Get Values
393 @param context: A standard dictionary for contextual values
398 model_obj = self.pool.get(context.get('model'))
402 exists, r_id = uid2openobjectid(cr, val['id'], context.get('model'), \
403 val.get('recurrent_id'))
404 if val.has_key('create_date'): val.pop('create_date')
405 u_id = val.get('id', None)
408 val.update({'recurrent_uid': exists})
409 model_obj.write(cr, uid, [r_id], val)
412 model_obj.write(cr, uid, [exists], val)
415 if u_id in recur_pool and val.get('recurrent_id'):
416 val.update({'recurrent_uid': recur_pool[u_id]})
417 revent_id = model_obj.create(cr, uid, val)
418 ids.append(revent_id)
420 event_id = model_obj.create(cr, uid, val)
421 recur_pool[u_id] = event_id
424 raise osv.except_osv(('Error !'), (str(e)))
427 def export_cal(self, cr, uid, datas, vobj=None, context=None):
429 @param self: The object pointer
430 @param cr: the current row, from the database cursor,
431 @param uid: the current user’s ID for security checks,
432 @param datas: Get Data's for caldav
433 @param context: A standard dictionary for contextual values
437 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
438 ical = vobject.iCalendar()
439 self.create_ics(cr, uid, datas, vobj, ical, context=context)
442 raise # osv.except_osv(('Error !'), (str(e)))
444 def import_cal(self, cr, uid, content, data_id=None, context=None):
446 @param self: The object pointer
447 @param cr: the current row, from the database cursor,
448 @param uid: the current user’s ID for security checks,
449 @param data_id: Get Data’s ID or False
450 @param context: A standard dictionary for contextual values
454 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
455 parsedCal = vobject.readOne(ical_data)
458 for child in parsedCal.getChildren():
459 if child.name.lower() in ('vevent', 'vtodo'):
460 vals = self.parse_ics(cr, uid, child, context=context)
464 if vals: res.append(vals)
465 self.ical_reset('value')
468 class Calendar(CalDAV, osv.osv):
469 _name = 'basic.calendar'
470 _calname = 'calendar'
473 'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
474 'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
475 # or the minimum and maximum range of the iCalendar specification
476 # that is required in order to interpret the iCalendar object.
477 'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
478 'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
479 'vevent': None, # Use: O-n, Type: Collection of Event class
480 'vtodo': None, # Use: O-n, Type: Collection of ToDo class
481 'vjournal': None, # Use: O-n, Type: Collection of Journal class
482 'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
483 'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
486 'name': fields.char("Name", size=64),
487 'user_id': fields.many2one('res.users', 'Owner'),
488 'collection_id': fields.many2one('document.directory', 'Collection', \
490 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
491 string="Type", size=64),
492 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
493 'create_date': fields.datetime('Created Date', readonly=True),
494 'write_date': fields.datetime('Modifided Date', readonly=True),
495 'description': fields.text("description"),
498 def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
504 ctx_res_id = context.get('res_id', None)
505 ctx_model = context.get('model', None)
506 for cal in self.browse(cr, uid, ids):
507 for line in cal.line_ids:
508 if ctx_model and ctx_model != line.object_id.model:
510 if line.name in ('valarm', 'attendee'):
512 line_domain = eval(line.domain or '[]')
513 line_domain += domain
515 line_domain += [('id','=',ctx_res_id)]
516 mod_obj = self.pool.get(line.object_id.model)
517 data_ids = mod_obj.search(cr, uid, line_domain, context=context)
518 for data in mod_obj.browse(cr, uid, data_ids, context):
519 ctx = parent and parent.context or None
520 node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
524 def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
526 @param ids: List of calendar’s IDs
527 @param vobj: the type of object to export
529 @return the ical data.
533 ctx_model = context.get('model', None)
534 ctx_res_id = context.get('res_id', None)
535 ical = vobject.iCalendar()
536 for cal in self.browse(cr, uid, ids):
537 for line in cal.line_ids:
538 if ctx_model and ctx_model != line.object_id.model:
540 if line.name in ('valarm', 'attendee'):
542 domain = eval(line.domain or '[]')
544 domain += [('id','=',ctx_res_id)]
545 mod_obj = self.pool.get(line.object_id.model)
546 data_ids = mod_obj.search(cr, uid, domain, context=context)
547 datas = mod_obj.read(cr, uid, data_ids, context=context)
548 context.update({'model': line.object_id.model,
549 'calendar_id': cal.id
551 self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
552 self.create_ics(cr, uid, datas, line.name, ical, context=context)
553 return ical.serialize()
555 def import_cal(self, cr, uid, content, data_id=None, context=None):
557 @param self: The object pointer
558 @param cr: the current row, from the database cursor,
559 @param uid: the current user’s ID for security checks,
560 @param data_id: Get Data’s ID or False
561 @param context: A standard dictionary for contextual values
568 parsedCal = vobject.readOne(ical_data)
570 data_id = self.search(cr, uid, [])[0]
571 cal = self.browse(cr, uid, data_id, context=context)
574 for line in cal.line_ids:
575 cal_children[line.name] = line.object_id.model
578 for child in parsedCal.getChildren():
579 if child.name.lower() in cal_children:
580 context.update({'model': cal_children[child.name.lower()],
581 'calendar_id': cal['id']
583 self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
584 val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
586 objs.append(cal_children[child.name.lower()])
589 for obj_name in list(set(objs)):
590 obj = self.pool.get(obj_name)
591 if hasattr(obj, 'check_import'):
592 r = obj.check_import(cr, uid, vals, context=context)
597 r = self.check_import(cr, uid, vals, context=context)
603 class basic_calendar_line(osv.osv):
604 """ Calendar Lines """
606 _name = 'basic.calendar.lines'
607 _description = 'Calendar Lines'
610 'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
611 ('valarm', 'Alarm'), \
612 ('attendee', 'Attendee')], \
613 string="Type", size=64),
614 'object_id': fields.many2one('ir.model', 'Object'),
615 'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
616 required=True, ondelete='cascade'),
617 'domain': fields.char('Domain', size=124),
618 'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
622 'domain': lambda *a: '[]',
625 def create(self, cr, uid, vals, context=None):
626 """ create calendar's line
627 @param self: The object pointer
628 @param cr: the current row, from the database cursor,
629 @param uid: the current user’s ID for security checks,
630 @param vals: Get the Values
631 @param context: A standard dictionary for contextual values
634 cr.execute("Select count(id) from basic_calendar_lines \
635 where name='%s' and calendar_id=%s" % (vals.get('name'), vals.get('calendar_id')))
639 raise osv.except_osv(_('Warning !'), _('Can not create \
640 line "%s" more than once' % (vals.get('name'))))
641 return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
643 basic_calendar_line()
646 class basic_calendar_attribute(osv.osv):
647 _name = 'basic.calendar.attributes'
648 _description = 'Calendar attributes'
650 'name': fields.char("Name", size=64, required=True),
651 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
652 ('alarm', 'Alarm'), \
653 ('attendee', 'Attendee')], \
654 string="Type", size=64, required=True),
657 basic_calendar_attribute()
660 class basic_calendar_fields(osv.osv):
661 """ Calendar fields """
663 _name = 'basic.calendar.fields'
664 _description = 'Calendar fields'
667 'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
668 'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
669 'type_id': fields.many2one('basic.calendar.lines', 'Type', \
670 required=True, ondelete='cascade'),
671 'expr': fields.char("Expression", size=64),
672 'fn': fields.selection([('field', 'Use the field'),
673 ('const', 'Expression as constant'),
674 ('hours', 'Interval in hours'),
676 'mapping': fields.text('Mapping'),
680 'fn': lambda *a: 'field',
684 ( 'name_type_uniq', 'UNIQUE(name, type_id)', 'Can not map a field more than once'),
687 def check_line(self, cr, uid, vals, name, context=None):
688 """ check calendar's line
689 @param self: The object pointer
690 @param cr: the current row, from the database cursor,
691 @param uid: the current user’s ID for security checks,
692 @param vals: Get Values
693 @param context: A standard dictionary for contextual values
695 f_obj = self.pool.get('ir.model.fields')
696 field = f_obj.browse(cr, uid, vals['field_id'], context=context)
697 relation = field.relation
698 line_obj = self.pool.get('basic.calendar.lines')
699 l_id = line_obj.search(cr, uid, [('name', '=', name)])
701 line = line_obj.browse(cr, uid, l_id, context=context)[0]
702 line_rel = line.object_id.model
703 if (relation != 'NULL') and (not relation == line_rel):
704 raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines' % (name)))
707 def create(self, cr, uid, vals, context=None):
708 """ Create Calendar's fields
709 @param self: The object pointer
710 @param cr: the current row, from the database cursor,
711 @param uid: the current user’s ID for security checks,
712 @param vals: Get Values
713 @param context: A standard dictionary for contextual values
716 cr.execute('select name from basic_calendar_attributes \
717 where id=%s' % (vals.get('name')))
720 if name in ('valarm', 'attendee'):
721 self.check_line(cr, uid, vals, name, context=context)
722 return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
724 def write(self, cr, uid, ids, vals, context=None):
725 """ write Calendar's fields
726 @param self: The object pointer
727 @param cr: the current row, from the database cursor,
728 @param uid: the current user’s ID for security checks,
729 @param vals: Get Values
730 @param context: A standard dictionary for contextual values
736 field = self.browse(cr, uid, id, context=context)
737 name = field.name.name
738 if name in ('valarm', 'attendee'):
739 self.check_line(cr, uid, vals, name, context=context)
740 return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
742 basic_calendar_fields()
745 class Event(CalDAV, osv.osv_memory):
746 _name = 'basic.calendar.event'
749 'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
750 '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.
751 'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
752 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
753 'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
754 '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.
755 'location': None, # Use: O-1, Type: TEXT Defines the intended venue for the activity defined by a calendar component.
756 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
757 'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
758 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
759 'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
760 'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
761 'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
762 'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
763 'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
764 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
766 'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
767 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
768 'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
769 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
770 'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a reference to contact information associated with the calendar component.
771 'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
772 'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
774 'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
775 # like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
776 '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
777 'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
778 'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
780 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
781 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
784 def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
786 @param self: The object pointer
787 @param cr: the current row, from the database cursor,
788 @param uid: the current user’s ID for security checks,
789 @param datas: Get datas
790 @param context: A standard dictionary for contextual values
793 return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
798 class ToDo(CalDAV, osv.osv_memory):
799 _name = 'basic.calendar.todo'
837 def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
839 @param self: The object pointer
840 @param cr: the current row, from the database cursor,
841 @param uid: the current user’s ID for security checks,
842 @param datas: Get datas
843 @param context: A standard dictionary for contextual values
846 return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
851 class Journal(CalDAV):
856 class FreeBusy(CalDAV):
858 'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a reference to contact information associated with the calendar component.
859 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
860 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
861 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
862 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
863 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
864 'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
865 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
866 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
867 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
868 'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
874 class Timezone(CalDAV, osv.osv_memory):
875 _name = 'basic.calendar.timezone'
876 _calname = 'vtimezone'
879 'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
880 '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.
881 '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.
882 'standardc': {'tzprop': None}, # Use: R-1,
883 'daylightc': {'tzprop': None}, # Use: R-1,
884 'x-prop': None, # Use: O-n, Type: Text,
887 def get_name_offset(self, cr, uid, tzid, context=None):
888 """ Get Name Offset value
889 @param self: The object pointer
890 @param cr: the current row, from the database cursor,
891 @param uid: the current user’s ID for security checks,
892 @param context: A standard dictionary for contextual values
895 mytz = pytz.timezone(tzid.title())
896 mydt = datetime.now(tz=mytz)
897 offset = mydt.utcoffset()
898 val = offset.days * 24 + float(offset.seconds) / 3600
899 realoffset = '%02d%02d' % (math.floor(abs(val)), \
900 round(abs(val) % 1 + 0.01, 2) * 60)
901 realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
902 return (mydt.tzname(), realoffset)
904 def export_cal(self, cr, uid, model, tzid, ical, context=None):
906 @param self: The object pointer
907 @param cr: the current row, from the database cursor,
908 @param uid: the current user’s ID for security checks,
909 @param model: Get Model's name
910 @param context: A standard dictionary for contextual values
915 ctx.update({'model': model})
916 cal_tz = ical.add('vtimezone')
917 cal_tz.add('TZID').value = tzid.title()
918 tz_std = cal_tz.add('STANDARD')
919 tzname, offset = self.get_name_offset(cr, uid, tzid)
920 tz_std.add("TZOFFSETFROM").value = offset
921 tz_std.add("TZOFFSETTO").value = offset
922 #TODO: Get start date for timezone
923 tz_std.add("DTSTART").value = datetime.strptime('1970-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')
924 tz_std.add("TZNAME").value = tzname
927 def import_cal(self, cr, uid, ical_data, context=None):
929 @param self: The object pointer
930 @param cr: the current row, from the database cursor,
931 @param uid: the current user’s ID for security checks,
932 @param ical_data: Get calendar's data
933 @param context: A standard dictionary for contextual values
936 for child in ical_data.getChildren():
937 if child.name.lower() == 'tzid':
939 self.ical_set(child.name.lower(), tzname, 'value')
940 vals = map_data(cr, uid, self, context=context)
946 class Alarm(CalDAV, osv.osv_memory):
947 _name = 'basic.calendar.alarm'
951 'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
952 '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
953 'summary': None, # Use: R-1, Type: Text Which contains the text to be used as the message subject. Use for EMAIL
954 '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
955 '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
956 '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
957 '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
958 '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.
962 def export_cal(self, cr, uid, model, alarm_id, vevent, context=None):
964 @param self: The object pointer
965 @param cr: the current row, from the database cursor,
966 @param uid: the current user’s ID for security checks,
967 @param model: Get Model's name
968 @param alarm_id: Get Alarm's Id
969 @param context: A standard dictionary for contextual values
973 valarm = vevent.add('valarm')
974 alarm_object = self.pool.get(model)
975 alarm_data = alarm_object.read(cr, uid, alarm_id, [])
977 # Compute trigger data
978 interval = alarm_data['trigger_interval']
979 occurs = alarm_data['trigger_occurs']
980 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
981 or -(alarm_data['trigger_duration'])
982 related = alarm_data['trigger_related']
983 trigger = valarm.add('TRIGGER')
984 trigger.params['related'] = [related.upper()]
985 if interval == 'days':
986 delta = timedelta(days=duration)
987 if interval == 'hours':
988 delta = timedelta(hours=duration)
989 if interval == 'minutes':
990 delta = timedelta(minutes=duration)
991 trigger.value = delta
993 # Compute other details
994 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
995 valarm.add('ACTION').value = alarm_data['action']
998 def import_cal(self, cr, uid, ical_data, context=None):
1000 @param self: The object pointer
1001 @param cr: the current row, from the database cursor,
1002 @param uid: the current user’s ID for security checks,
1003 @param ical_data: Get calendar's Data
1004 @param context: A standard dictionary for contextual values
1007 ctx = context.copy()
1008 ctx.update({'model': context.get('model', None)})
1009 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1010 for child in ical_data.getChildren():
1011 if child.name.lower() == 'trigger':
1012 seconds = child.value.seconds
1013 days = child.value.days
1014 diff = (days * 86400) + seconds
1018 duration = abs(days)
1019 related = days > 0 and 'after' or 'before'
1020 elif (abs(diff) / 3600) == 0:
1021 duration = abs(diff / 60)
1022 interval = 'minutes'
1023 related = days >= 0 and 'after' or 'before'
1025 duration = abs(diff / 3600)
1027 related = days >= 0 and 'after' or 'before'
1028 self.ical_set('trigger_interval', interval, 'value')
1029 self.ical_set('trigger_duration', duration, 'value')
1030 self.ical_set('trigger_occurs', related.lower(), 'value')
1032 if child.params.get('related'):
1033 self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
1035 self.ical_set(child.name.lower(), child.value.lower(), 'value')
1036 vals = map_data(cr, uid, self, context=context)
1042 class Attendee(CalDAV, osv.osv_memory):
1043 _name = 'basic.calendar.attendee'
1044 _calname = 'attendee'
1047 'cutype': None, # Use: 0-1 Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
1048 'member': None, # Use: 0-1 Specify the group or list membership of the calendar user specified by the property.
1049 '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"
1050 '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".
1051 '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.
1052 'delegated-to': None, # Use: 0-1 Specify the calendar users to whom the calendar user specified by the property has delegated participation.
1053 'delegated-from': None, # Use: 0-1 Specify the calendar users that have delegated their participation to the calendar user specified by the property.
1054 'sent-by': None, # Use: 0-1 Specify the calendar user that is acting on behalf of the calendar user specified by the property.
1055 'cn': None, # Use: 0-1 Specify the common name to be associated with the calendar user specified by the property.
1056 'dir': None, # Use: 0-1 Specify reference to a directory entry associated with the calendar user specified by the property.
1057 'language': None, # Use: 0-1 Specify the language for text values in a property or property parameter.
1060 def import_cal(self, cr, uid, ical_data, context=None):
1062 @param self: The object pointer
1063 @param cr: the current row, from the database cursor,
1064 @param uid: the current user’s ID for security checks,
1065 @param ical_data: Get calendar's Data
1066 @param context: A standard dictionary for contextual values
1069 ctx = context.copy()
1070 ctx.update({'model': context.get('model', None)})
1071 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1072 for para in ical_data.params:
1073 if para.lower() == 'cn':
1074 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
1075 ical_data.value, 'value')
1077 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
1078 if not ical_data.params.get('CN'):
1079 self.ical_set('cn', ical_data.value, 'value')
1080 vals = map_data(cr, uid, self, context=context)
1083 def export_cal(self, cr, uid, model, attendee_ids, vevent, context=None):
1085 @param self: The object pointer
1086 @param cr: the current row, from the database cursor,
1087 @param uid: the current user’s ID for security checks,
1088 @param model: Get model's name
1089 @param attendee_ids: Get Attendee's Id
1090 @param context: A standard dictionary for contextual values
1094 attendee_object = self.pool.get(model)
1095 ctx = context.copy()
1096 ctx.update({'model': model})
1097 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1098 for attendee in attendee_object.read(cr, uid, attendee_ids, []):
1099 attendee_add = vevent.add('attendee')
1101 for a_key, a_val in self.__attribute__.items():
1102 if attendee[a_val['field']] and a_val['field'] != 'cn':
1103 if a_val['type'] in ('text', 'char', 'selection'):
1104 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1105 elif a_val['type'] == 'boolean':
1106 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1107 if a_val['field'] == 'cn' and attendee[a_val['field']]:
1108 cn_val = [str(attendee[a_val['field']])]
1110 attendee_add.params['CN'] = cn_val
1111 if not attendee['email']:
1112 attendee_add.value = 'MAILTO:'
1113 #raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
1114 elif attendee['email']:
1115 attendee_add.value = 'MAILTO:' + attendee['email']
1120 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: