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 self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
270 date_utc = cal_data.value.astimezone(pytz.utc)
271 self.ical_set(cal_data.name.lower(), date_utc, 'value')
273 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
274 vals = map_data(cr, uid, self, context=context)
277 def create_ics(self, cr, uid, datas, name, ical, context=None):
278 """ create calendaring and scheduling information
279 @param self: The object pointer
280 @param cr: the current row, from the database cursor,
281 @param uid: the current user’s ID for security checks,
282 @param context: A standard dictionary for contextual values """
291 vevent = ical.add(name)
292 for field in self.__attribute__.keys():
293 map_field = self.ical_get(field, 'field')
294 map_type = self.ical_get(field, 'type')
295 if map_field in data.keys():
297 model = context.get('model', None)
300 uidval = openobjectid2uid(cr, data[map_field], model)
301 model_obj = self.pool.get(model)
303 if model_obj._columns.get('recurrent_uid', None):
304 cr.execute('select id from %s where recurrent_uid=%s'
305 % (model_obj._table, data[map_field]))
306 r_ids = map(lambda x: x[0], cr.fetchall())
308 rcal = self.export_cal(cr, uid, r_ids, 'vevent', context=context)
309 if data.get('recurrent_uid', None):
310 uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
311 vevent.add('uid').value = uidval
312 elif field == 'attendee' and data[map_field]:
313 model = self.__attribute__[field].get('object', False)
314 attendee_obj = self.pool.get('basic.calendar.attendee')
315 vevent = attendee_obj.export_cal(cr, uid, model, \
316 data[map_field], vevent, context=context)
317 elif field == 'valarm' and data[map_field]:
318 model = self.__attribute__[field].get('object', False)
320 ctx.update({'model': model})
321 alarm_obj = self.pool.get('basic.calendar.alarm')
322 vevent = alarm_obj.export_cal(cr, uid, model, \
323 data[map_field][0], vevent, context=ctx)
324 elif field == 'vtimezone' and data[map_field]:
325 tzval = data[map_field]
326 if tzval not in timezones:
327 tz_obj = self.pool.get('basic.calendar.timezone')
328 ical = tz_obj.export_cal(cr, uid, None, \
329 data[map_field], ical, context=context)
330 timezones.append(data[map_field])
332 exfield.params['TZID'] = [tzval.title()]
334 for exdate in exdates:
335 date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
336 dest_date = self.format_date_tz(date1, tzval.title())
337 ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
338 exdates_updated.append(ex_date)
339 exfield.value = map(parser.parse, exdates_updated)
340 elif field == 'organizer' and data[map_field]:
341 organizer = data[map_field]
342 event_org = vevent.add('organizer')
343 event_org.params['CN'] = [organizer]
344 event_org.value = 'MAILTO:' + (organizer)
345 elif data[map_field]:
346 if map_type in ("char", "text"):
347 if field in ('exdate'):
348 exfield = vevent.add(field)
349 exdates = (data[map_field]).split(',')
351 exfield.params['TZID'] = [tzval.title()]
353 for exdate in exdates:
354 date1 = (datetime.strptime(exdate, "%Y%m%dT%H%M%S")).strftime('%Y-%m-%d %H:%M:%S')
355 dest_date = self.format_date_tz(date1, tzval.title())
356 ex_date = (datetime.strptime(dest_date, "%Y-%m-%d %H:%M:%S")).strftime('%Y%m%dT%H%M%S')
357 exdates_updated.append(ex_date)
358 exdates = exdates_updated
359 exfield.value = map(parser.parse, exdates)
361 vevent.add(field).value = tools.ustr(data[map_field])
362 elif map_type in ('datetime', 'date') and data[map_field]:
363 dtfield = vevent.add(field)
365 dest_date = self.format_date_tz(data[map_field], tzval.title())
366 dtfield.params['TZID'] = [tzval.title()]
367 dtfield.value = parser.parse(dest_date)
369 dtfield.value = parser.parse(data[map_field])
370 elif map_type == "timedelta":
371 vevent.add(field).value = timedelta(hours=data[map_field])
372 elif map_type == "many2one":
373 vevent.add(field).value = tools.ustr(data.get(map_field)[1])
374 elif map_type in ("float", "integer"):
375 vevent.add(field).value = str(data.get(map_field))
376 elif map_type == "selection":
377 if not self.ical_get(field, 'mapping'):
378 vevent.add(field).value = (tools.ustr(data[map_field])).upper()
380 for key1, val1 in self.ical_get(field, 'mapping').items():
381 if val1 == data[map_field]:
382 vevent.add(field).value = key1
385 def check_import(self, cr, uid, vals, context=None):
387 @param self: The object pointer
388 @param cr: the current row, from the database cursor,
389 @param uid: the current user’s ID for security checks,
390 @param vals: Get Values
391 @param context: A standard dictionary for contextual values
396 model_obj = self.pool.get(context.get('model'))
400 exists, r_id = uid2openobjectid(cr, val['id'], context.get('model'), \
401 val.get('recurrent_id'))
402 if val.has_key('create_date'): val.pop('create_date')
403 u_id = val.get('id', None)
406 val.update({'recurrent_uid': exists})
407 model_obj.write(cr, uid, [r_id], val)
410 model_obj.write(cr, uid, [exists], val)
413 if u_id in recur_pool and val.get('recurrent_id'):
414 val.update({'recurrent_uid': recur_pool[u_id]})
415 revent_id = model_obj.create(cr, uid, val)
416 ids.append(revent_id)
418 event_id = model_obj.create(cr, uid, val)
419 recur_pool[u_id] = event_id
425 def export_cal(self, cr, uid, datas, vobj=None, context=None):
427 @param self: The object pointer
428 @param cr: the current row, from the database cursor,
429 @param uid: the current user’s ID for security checks,
430 @param datas: Get Data's for caldav
431 @param context: A standard dictionary for contextual values
435 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
436 ical = vobject.iCalendar()
437 self.create_ics(cr, uid, datas, vobj, ical, context=context)
440 raise # osv.except_osv(('Error !'), (str(e)))
442 def import_cal(self, cr, uid, content, data_id=None, context=None):
444 @param self: The object pointer
445 @param cr: the current row, from the database cursor,
446 @param uid: the current user’s ID for security checks,
447 @param data_id: Get Data’s ID or False
448 @param context: A standard dictionary for contextual values
452 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
453 parsedCal = vobject.readOne(ical_data)
456 for child in parsedCal.getChildren():
457 if child.name.lower() in ('vevent', 'vtodo'):
458 vals = self.parse_ics(cr, uid, child, context=context)
462 if vals: res.append(vals)
463 self.ical_reset('value')
466 class Calendar(CalDAV, osv.osv):
467 _name = 'basic.calendar'
468 _calname = 'calendar'
471 'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
472 'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
473 # or the minimum and maximum range of the iCalendar specification
474 # that is required in order to interpret the iCalendar object.
475 'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
476 'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
477 'vevent': None, # Use: O-n, Type: Collection of Event class
478 'vtodo': None, # Use: O-n, Type: Collection of ToDo class
479 'vjournal': None, # Use: O-n, Type: Collection of Journal class
480 'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
481 'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
484 'name': fields.char("Name", size=64),
485 'user_id': fields.many2one('res.users', 'Owner'),
486 'collection_id': fields.many2one('document.directory', 'Collection', \
488 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
489 string="Type", size=64),
490 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
491 'create_date': fields.datetime('Created Date', readonly=True),
492 'write_date': fields.datetime('Modifided Date', readonly=True),
493 'description': fields.text("description"),
496 def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
502 ctx_res_id = context.get('res_id', None)
503 ctx_model = context.get('model', None)
504 for cal in self.browse(cr, uid, ids):
505 for line in cal.line_ids:
506 if ctx_model and ctx_model != line.object_id.model:
508 if line.name in ('valarm', 'attendee'):
510 line_domain = eval(line.domain or '[]')
511 line_domain += domain
513 line_domain += [('id','=',ctx_res_id)]
514 mod_obj = self.pool.get(line.object_id.model)
515 data_ids = mod_obj.search(cr, uid, line_domain, context=context)
516 for data in mod_obj.browse(cr, uid, data_ids, context):
517 ctx = parent and parent.context or None
518 node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
522 def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
524 @param ids: List of calendar’s IDs
525 @param vobj: the type of object to export
527 @return the ical data.
531 ctx_model = context.get('model', None)
532 ctx_res_id = context.get('res_id', None)
533 ical = vobject.iCalendar()
534 for cal in self.browse(cr, uid, ids):
535 for line in cal.line_ids:
536 if ctx_model and ctx_model != line.object_id.model:
538 if line.name in ('valarm', 'attendee'):
540 domain = eval(line.domain or '[]')
542 domain += [('id','=',ctx_res_id)]
543 mod_obj = self.pool.get(line.object_id.model)
544 data_ids = mod_obj.search(cr, uid, domain, context=context)
545 datas = mod_obj.read(cr, uid, data_ids, context=context)
546 context.update({'model': line.object_id.model,
547 'calendar_id': cal.id
549 self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
550 self.create_ics(cr, uid, datas, line.name, ical, context=context)
551 return ical.serialize()
553 def import_cal(self, cr, uid, content, data_id=None, context=None):
555 @param self: The object pointer
556 @param cr: the current row, from the database cursor,
557 @param uid: the current user’s ID for security checks,
558 @param data_id: Get Data’s ID or False
559 @param context: A standard dictionary for contextual values
566 parsedCal = vobject.readOne(ical_data)
568 data_id = self.search(cr, uid, [])[0]
569 cal = self.browse(cr, uid, data_id, context=context)
572 for line in cal.line_ids:
573 cal_children[line.name] = line.object_id.model
576 for child in parsedCal.getChildren():
577 if child.name.lower() in cal_children:
578 context.update({'model': cal_children[child.name.lower()],
579 'calendar_id': cal['id']
581 self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
582 val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
584 objs.append(cal_children[child.name.lower()])
587 for obj_name in list(set(objs)):
588 obj = self.pool.get(obj_name)
589 if hasattr(obj, 'check_import'):
590 r = obj.check_import(cr, uid, vals, context=context)
595 r = self.check_import(cr, uid, vals, context=context)
601 class basic_calendar_line(osv.osv):
602 """ Calendar Lines """
604 _name = 'basic.calendar.lines'
605 _description = 'Calendar Lines'
608 'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
609 ('valarm', 'Alarm'), \
610 ('attendee', 'Attendee')], \
611 string="Type", size=64),
612 'object_id': fields.many2one('ir.model', 'Object'),
613 'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
614 required=True, ondelete='cascade'),
615 'domain': fields.char('Domain', size=124),
616 'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
620 'domain': lambda *a: '[]',
623 def create(self, cr, uid, vals, context=None):
624 """ create calendar's line
625 @param self: The object pointer
626 @param cr: the current row, from the database cursor,
627 @param uid: the current user’s ID for security checks,
628 @param vals: Get the Values
629 @param context: A standard dictionary for contextual values
632 cr.execute("Select count(id) from basic_calendar_lines \
633 where name='%s' and calendar_id=%s" % (vals.get('name'), vals.get('calendar_id')))
637 raise osv.except_osv(_('Warning !'), _('Can not create \
638 line "%s" more than once' % (vals.get('name'))))
639 return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
641 basic_calendar_line()
644 class basic_calendar_attribute(osv.osv):
645 _name = 'basic.calendar.attributes'
646 _description = 'Calendar attributes'
648 'name': fields.char("Name", size=64, required=True),
649 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
650 ('alarm', 'Alarm'), \
651 ('attendee', 'Attendee')], \
652 string="Type", size=64, required=True),
655 basic_calendar_attribute()
658 class basic_calendar_fields(osv.osv):
659 """ Calendar fields """
661 _name = 'basic.calendar.fields'
662 _description = 'Calendar fields'
665 'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
666 'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
667 'type_id': fields.many2one('basic.calendar.lines', 'Type', \
668 required=True, ondelete='cascade'),
669 'expr': fields.char("Expression", size=64),
670 'fn': fields.selection([('field', 'Use the field'),
671 ('const', 'Expression as constant'),
672 ('hours', 'Interval in hours'),
674 'mapping': fields.text('Mapping'),
678 'fn': lambda *a: 'field',
682 ( 'name_type_uniq', 'UNIQUE(name, type_id)', 'Can not map a field more than once'),
685 def check_line(self, cr, uid, vals, name, context=None):
686 """ check calendar's line
687 @param self: The object pointer
688 @param cr: the current row, from the database cursor,
689 @param uid: the current user’s ID for security checks,
690 @param vals: Get Values
691 @param context: A standard dictionary for contextual values
693 f_obj = self.pool.get('ir.model.fields')
694 field = f_obj.browse(cr, uid, vals['field_id'], context=context)
695 relation = field.relation
696 line_obj = self.pool.get('basic.calendar.lines')
697 l_id = line_obj.search(cr, uid, [('name', '=', name)])
699 line = line_obj.browse(cr, uid, l_id, context=context)[0]
700 line_rel = line.object_id.model
701 if (relation != 'NULL') and (not relation == line_rel):
702 raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines' % (name)))
705 def create(self, cr, uid, vals, context=None):
706 """ Create Calendar's fields
707 @param self: The object pointer
708 @param cr: the current row, from the database cursor,
709 @param uid: the current user’s ID for security checks,
710 @param vals: Get Values
711 @param context: A standard dictionary for contextual values
714 cr.execute('select name from basic_calendar_attributes \
715 where id=%s' % (vals.get('name')))
718 if name in ('valarm', 'attendee'):
719 self.check_line(cr, uid, vals, name, context=context)
720 return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
722 def write(self, cr, uid, ids, vals, context=None):
723 """ write Calendar's fields
724 @param self: The object pointer
725 @param cr: the current row, from the database cursor,
726 @param uid: the current user’s ID for security checks,
727 @param vals: Get Values
728 @param context: A standard dictionary for contextual values
734 field = self.browse(cr, uid, id, context=context)
735 name = field.name.name
736 if name in ('valarm', 'attendee'):
737 self.check_line(cr, uid, vals, name, context=context)
738 return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
740 basic_calendar_fields()
743 class Event(CalDAV, osv.osv_memory):
744 _name = 'basic.calendar.event'
747 'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
748 '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.
749 'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
750 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
751 'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
752 '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.
753 'location': None, # Use: O-1, Type: TEXT Defines the intended venue for the activity defined by a calendar component.
754 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
755 'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
756 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
757 'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
758 'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
759 'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
760 'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
761 'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
762 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
764 'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
765 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
766 'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
767 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
768 'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a reference to contact information associated with the calendar component.
769 'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
770 'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
772 'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
773 # like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
774 '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
775 'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
776 'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
778 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
779 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
782 def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
784 @param self: The object pointer
785 @param cr: the current row, from the database cursor,
786 @param uid: the current user’s ID for security checks,
787 @param datas: Get datas
788 @param context: A standard dictionary for contextual values
791 return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
796 class ToDo(CalDAV, osv.osv_memory):
797 _name = 'basic.calendar.todo'
835 def export_cal(self, cr, uid, datas, vobj='vevent', context=None):
837 @param self: The object pointer
838 @param cr: the current row, from the database cursor,
839 @param uid: the current user’s ID for security checks,
840 @param datas: Get datas
841 @param context: A standard dictionary for contextual values
844 return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
849 class Journal(CalDAV):
854 class FreeBusy(CalDAV):
856 'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a reference to contact information associated with the calendar component.
857 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
858 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
859 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
860 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
861 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
862 'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
863 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
864 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
865 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
866 'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
872 class Timezone(CalDAV, osv.osv_memory):
873 _name = 'basic.calendar.timezone'
874 _calname = 'vtimezone'
877 'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
878 '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.
879 '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.
880 'standardc': {'tzprop': None}, # Use: R-1,
881 'daylightc': {'tzprop': None}, # Use: R-1,
882 'x-prop': None, # Use: O-n, Type: Text,
885 def get_name_offset(self, cr, uid, tzid, context=None):
886 """ Get Name Offset value
887 @param self: The object pointer
888 @param cr: the current row, from the database cursor,
889 @param uid: the current user’s ID for security checks,
890 @param context: A standard dictionary for contextual values
893 mytz = pytz.timezone(tzid.title())
894 mydt = datetime.now(tz=mytz)
895 offset = mydt.utcoffset()
896 val = offset.days * 24 + float(offset.seconds) / 3600
897 realoffset = '%02d%02d' % (math.floor(abs(val)), \
898 round(abs(val) % 1 + 0.01, 2) * 60)
899 realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
900 return (mydt.tzname(), realoffset)
902 def export_cal(self, cr, uid, model, tzid, ical, context=None):
904 @param self: The object pointer
905 @param cr: the current row, from the database cursor,
906 @param uid: the current user’s ID for security checks,
907 @param model: Get Model's name
908 @param context: A standard dictionary for contextual values
913 ctx.update({'model': model})
914 cal_tz = ical.add('vtimezone')
915 cal_tz.add('TZID').value = tzid.title()
916 tz_std = cal_tz.add('STANDARD')
917 tzname, offset = self.get_name_offset(cr, uid, tzid)
918 tz_std.add("TZOFFSETFROM").value = offset
919 tz_std.add("TZOFFSETTO").value = offset
920 #TODO: Get start date for timezone
921 tz_std.add("DTSTART").value = datetime.strptime('1970-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')
922 tz_std.add("TZNAME").value = tzname
925 def import_cal(self, cr, uid, ical_data, context=None):
927 @param self: The object pointer
928 @param cr: the current row, from the database cursor,
929 @param uid: the current user’s ID for security checks,
930 @param ical_data: Get calendar's data
931 @param context: A standard dictionary for contextual values
934 for child in ical_data.getChildren():
935 if child.name.lower() == 'tzid':
937 self.ical_set(child.name.lower(), tzname, 'value')
938 vals = map_data(cr, uid, self, context=context)
944 class Alarm(CalDAV, osv.osv_memory):
945 _name = 'basic.calendar.alarm'
949 'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
950 '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
951 'summary': None, # Use: R-1, Type: Text Which contains the text to be used as the message subject. Use for EMAIL
952 '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
953 '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
954 '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
955 '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
956 '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.
960 def export_cal(self, cr, uid, model, alarm_id, vevent, context=None):
962 @param self: The object pointer
963 @param cr: the current row, from the database cursor,
964 @param uid: the current user’s ID for security checks,
965 @param model: Get Model's name
966 @param alarm_id: Get Alarm's Id
967 @param context: A standard dictionary for contextual values
971 valarm = vevent.add('valarm')
972 alarm_object = self.pool.get(model)
973 alarm_data = alarm_object.read(cr, uid, alarm_id, [])
975 # Compute trigger data
976 interval = alarm_data['trigger_interval']
977 occurs = alarm_data['trigger_occurs']
978 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
979 or -(alarm_data['trigger_duration'])
980 related = alarm_data['trigger_related']
981 trigger = valarm.add('TRIGGER')
982 trigger.params['related'] = [related.upper()]
983 if interval == 'days':
984 delta = timedelta(days=duration)
985 if interval == 'hours':
986 delta = timedelta(hours=duration)
987 if interval == 'minutes':
988 delta = timedelta(minutes=duration)
989 trigger.value = delta
991 # Compute other details
992 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
993 valarm.add('ACTION').value = alarm_data['action']
996 def import_cal(self, cr, uid, ical_data, context=None):
998 @param self: The object pointer
999 @param cr: the current row, from the database cursor,
1000 @param uid: the current user’s ID for security checks,
1001 @param ical_data: Get calendar's Data
1002 @param context: A standard dictionary for contextual values
1005 ctx = context.copy()
1006 ctx.update({'model': context.get('model', None)})
1007 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1008 for child in ical_data.getChildren():
1009 if child.name.lower() == 'trigger':
1010 seconds = child.value.seconds
1011 days = child.value.days
1012 diff = (days * 86400) + seconds
1016 duration = abs(days)
1017 related = days > 0 and 'after' or 'before'
1018 elif (abs(diff) / 3600) == 0:
1019 duration = abs(diff / 60)
1020 interval = 'minutes'
1021 related = days >= 0 and 'after' or 'before'
1023 duration = abs(diff / 3600)
1025 related = days >= 0 and 'after' or 'before'
1026 self.ical_set('trigger_interval', interval, 'value')
1027 self.ical_set('trigger_duration', duration, 'value')
1028 self.ical_set('trigger_occurs', related.lower(), 'value')
1030 if child.params.get('related'):
1031 self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
1033 self.ical_set(child.name.lower(), child.value.lower(), 'value')
1034 vals = map_data(cr, uid, self, context=context)
1040 class Attendee(CalDAV, osv.osv_memory):
1041 _name = 'basic.calendar.attendee'
1042 _calname = 'attendee'
1045 'cutype': None, # Use: 0-1 Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
1046 'member': None, # Use: 0-1 Specify the group or list membership of the calendar user specified by the property.
1047 '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"
1048 '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".
1049 '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.
1050 'delegated-to': None, # Use: 0-1 Specify the calendar users to whom the calendar user specified by the property has delegated participation.
1051 'delegated-from': None, # Use: 0-1 Specify the calendar users that have delegated their participation to the calendar user specified by the property.
1052 'sent-by': None, # Use: 0-1 Specify the calendar user that is acting on behalf of the calendar user specified by the property.
1053 'cn': None, # Use: 0-1 Specify the common name to be associated with the calendar user specified by the property.
1054 'dir': None, # Use: 0-1 Specify reference to a directory entry associated with the calendar user specified by the property.
1055 'language': None, # Use: 0-1 Specify the language for text values in a property or property parameter.
1058 def import_cal(self, cr, uid, ical_data, context=None):
1060 @param self: The object pointer
1061 @param cr: the current row, from the database cursor,
1062 @param uid: the current user’s ID for security checks,
1063 @param ical_data: Get calendar's Data
1064 @param context: A standard dictionary for contextual values
1067 ctx = context.copy()
1068 ctx.update({'model': context.get('model', None)})
1069 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1070 for para in ical_data.params:
1071 if para.lower() == 'cn':
1072 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
1073 ical_data.value, 'value')
1075 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
1076 if not ical_data.params.get('CN'):
1077 self.ical_set('cn', ical_data.value, 'value')
1078 vals = map_data(cr, uid, self, context=context)
1081 def export_cal(self, cr, uid, model, attendee_ids, vevent, context=None):
1083 @param self: The object pointer
1084 @param cr: the current row, from the database cursor,
1085 @param uid: the current user’s ID for security checks,
1086 @param model: Get model's name
1087 @param attendee_ids: Get Attendee's Id
1088 @param context: A standard dictionary for contextual values
1092 attendee_object = self.pool.get(model)
1093 ctx = context.copy()
1094 ctx.update({'model': model})
1095 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1096 for attendee in attendee_object.read(cr, uid, attendee_ids, []):
1097 attendee_add = vevent.add('attendee')
1099 for a_key, a_val in self.__attribute__.items():
1100 if attendee[a_val['field']] and a_val['field'] != 'cn':
1101 if a_val['type'] in ('text', 'char', 'selection'):
1102 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1103 elif a_val['type'] == 'boolean':
1104 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1105 if a_val['field'] == 'cn' and attendee[a_val['field']]:
1106 cn_val = [str(attendee[a_val['field']])]
1108 attendee_add.params['CN'] = cn_val
1109 if not attendee['email']:
1110 attendee_add.value = 'MAILTO:'
1111 #raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
1112 elif attendee['email']:
1113 attendee_add.value = 'MAILTO:' + attendee['email']
1118 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: