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 _
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+)$')
54 wematch = __rege.match(uidval.encode('utf8'))
58 model, id, dbname = wematch.groups()
59 model_obj = pooler.get_pool(cr.dbname).get(model)
60 if (not model == oomodel) or (not dbname == cr.dbname):
62 qry = 'select distinct(id) from %s' % model_obj._table
64 qry += " where recurrent_id='%s'" % (rdate)
70 ids = map(lambda x: str(x[0]), cr.fetchall())
75 def openobjectid2uid(cr, uidval, oomodel):
76 """ Open Object Id To UId
77 @param cr: the current row, from the database cursor,
78 @param uidval: Get USerId vale
79 @oomodel: Open Object ModelName """
81 value = 'OpenObject-%s_%s@%s' % (oomodel, uidval, cr.dbname)
84 def get_attribute_mapping(cr, uid, calname, context={}):
85 """ Attribute Mapping with Basic calendar fields and lines
86 @param cr: the current row, from the database cursor,
87 @param uid: the current user’s ID for security checks,
88 @param calname: Get Calendar name
89 @param context: A standard dictionary for contextual values """
93 pool = pooler.get_pool(cr.dbname)
94 field_obj = pool.get('basic.calendar.fields')
95 type_obj = pool.get('basic.calendar.lines')
96 domain = [('object_id.model', '=', context.get('model'))]
97 if context.get('calendar_id'):
98 domain.append(('calendar_id', '=', context.get('calendar_id')))
99 type_id = type_obj.search(cr, uid, domain)
100 fids = field_obj.search(cr, uid, [('type_id', '=', type_id[0])])
102 for field in field_obj.browse(cr, uid, fids):
103 attr = field.name.name
105 res[attr]['field'] = field.field_id.name
106 res[attr]['type'] = field.field_id.ttype
107 if field.fn == 'hours':
108 res[attr]['type'] = "timedelta"
109 if res[attr]['type'] in ('one2many', 'many2many', 'many2one'):
110 res[attr]['object'] = field.field_id.relation
111 elif res[attr]['type'] in ('selection') and field.mapping:
112 res[attr]['mapping'] = eval(field.mapping)
113 if not res.get('uid', None):
115 res['uid']['field'] = 'id'
116 res['uid']['type'] = "integer"
119 def map_data(cr, uid, obj):
121 @param self: The object pointer
122 @param cr: the current row, from the database cursor,"""
125 for map_dict in obj.__attribute__:
126 map_val = obj.ical_get(map_dict, 'value')
127 field = obj.ical_get(map_dict, 'field')
128 field_type = obj.ical_get(map_dict, 'type')
130 if field_type == 'selection':
133 mapping = obj.__attribute__[map_dict].get('mapping', False)
135 map_val = mapping[map_val.lower()]
137 map_val = map_val.lower()
138 if field_type == 'many2many':
143 model = obj.__attribute__[map_dict].get('object', False)
144 modobj = obj.pool.get(model)
145 for map_vall in map_val:
146 id = modobj.create(cr, uid, map_vall)
148 vals[field] = [(6, 0, ids)]
150 if field_type == 'many2one':
152 if not map_val or not isinstance(map_val, dict):
155 model = obj.__attribute__[map_dict].get('object', False)
156 modobj = obj.pool.get(model)
157 id = modobj.create(cr, uid, map_val)
160 if field_type == 'timedelta':
162 vals[field] = (map_val.seconds/float(86400) + map_val.days)
163 vals[field] = map_val
166 class CalDAV(object):
171 def ical_set(self, name, value, type):
172 """ set calendar Attribute
173 @param self: The object pointer,
174 @param name: Get Attribute Name
175 @param value: Get Attribute Value
176 @param type: Get Attribute Type
178 if name in self.__attribute__ and self.__attribute__[name]:
179 self.__attribute__[name][type] = value
182 def ical_get(self, name, type):
183 """ Get calendar Attribute
184 @param self: The object pointer,
185 @param name: Get Attribute Name
186 @param type: Get Attribute Type
189 if self.__attribute__.get(name):
190 val = self.__attribute__.get(name).get(type, None)
191 valtype = self.__attribute__.get(name).get('type', None)
193 if valtype and valtype == 'datetime' and val:
194 if isinstance(val, list):
195 val = ','.join(map(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), val))
197 val = val.strftime('%Y-%m-%d %H:%M:%S')
200 return self.__attribute__.get(name, None)
202 def ical_reset(self, type):
203 """ Reset Calendar Attribute
204 @param self: The object pointer,
205 @param type: Get Attribute Type
208 for name in self.__attribute__:
209 if self.__attribute__[name]:
210 self.__attribute__[name][type] = None
213 def parse_ics(self, cr, uid, child, cal_children=None, context=None):
214 """ parse calendaring and scheduling information
215 @param self: The object pointer
216 @param cr: the current row, from the database cursor,
217 @param uid: the current user’s ID for security checks,
218 @param context: A standard dictionary for contextual values """
221 for cal_data in child.getChildren():
222 if cal_data.name.lower() == 'attendee':
225 ctx.update({'model': cal_children[cal_data.name.lower()]})
226 attendee = self.pool.get('basic.calendar.attendee')
227 att_data.append(attendee.import_cal(cr, uid, cal_data, context=ctx))
228 self.ical_set(cal_data.name.lower(), att_data, 'value')
230 if cal_data.name.lower() == 'valarm':
231 alarm = self.pool.get('basic.calendar.alarm')
234 ctx.update({'model': cal_children[cal_data.name.lower()]})
235 vals = alarm.import_cal(cr, uid, cal_data, context=ctx)
236 self.ical_set(cal_data.name.lower(), vals, 'value')
238 if cal_data.name.lower() == 'exdate':
239 exval = map(lambda x: str(x), cal_data.value)
240 self.ical_set(cal_data.name.lower(), ','.join(exval), 'value')
242 if cal_data.name.lower() in self.__attribute__:
244 if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
245 self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
246 self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
247 vals = map_data(cr, uid, self)
250 def create_ics(self, cr, uid, datas, name, ical, context=None):
251 """ create calendaring and scheduling information
252 @param self: The object pointer
253 @param cr: the current row, from the database cursor,
254 @param uid: the current user’s ID for security checks,
255 @param context: A standard dictionary for contextual values """
262 vevent = ical.add(name)
263 for field in self.__attribute__.keys():
264 map_field = self.ical_get(field, 'field')
265 map_type = self.ical_get(field, 'type')
266 if map_field in data.keys():
268 model = context.get('model', None)
271 uidval = openobjectid2uid(cr, data[map_field], model)
272 model_obj = self.pool.get(model)
274 if model_obj._columns.get('recurrent_uid', None):
275 cr.execute('select id from %s where recurrent_uid=%s'
276 % (model_obj._table, data[map_field]))
277 r_ids = map(lambda x: x[0], cr.fetchall())
279 rdata = self.pool.get(model).read(cr, uid, r_ids)
280 event_obj = self.pool.get('basic.calendar.event')
281 rcal = event_obj.export_cal(cr, uid, rdata, context=context)
282 for revents in rcal.contents['vevent']:
283 ical.contents['vevent'].append(revents)
284 if data.get('recurrent_uid', None):
285 uidval = openobjectid2uid(cr, data['recurrent_uid'], model)
286 vevent.add('uid').value = uidval
287 elif field == 'attendee' and data[map_field]:
288 model = self.__attribute__[field].get('object', False)
289 attendee_obj = self.pool.get('basic.calendar.attendee')
290 vevent = attendee_obj.export_cal(cr, uid, model, \
291 data[map_field], vevent, context=context)
292 elif field == 'valarm' and data[map_field]:
293 model = self.__attribute__[field].get('object', False)
295 ctx.update({'model': model})
296 alarm_obj = self.pool.get('basic.calendar.alarm')
297 vevent = alarm_obj.export_cal(cr, uid, model, \
298 data[map_field][0], vevent, context=ctx)
299 elif field == 'vtimezone' and data[map_field] and data[map_field] not in timezones:
300 tzval = data[map_field]
301 tz_obj = self.pool.get('basic.calendar.timezone')
302 ical = tz_obj.export_cal(cr, uid, None, \
303 data[map_field], ical, context=context)
304 timezones.append(data[map_field])
305 elif data[map_field]:
306 if map_type in ("char", "text"):
307 if field in ('exdate'):
308 vevent.add(field).value = map(parser.parse, (data[map_field]).split(','))
310 vevent.add(field).value = tools.ustr(data[map_field])
311 elif map_type in ('datetime', 'date') and data[map_field]:
312 dtfield = vevent.add(field)
313 dtfield.value = parser.parse(data[map_field])
315 dtfield.params['TZID'] = [tzval.title()]
316 elif map_type == "timedelta":
317 vevent.add(field).value = timedelta(hours=data[map_field])
318 elif map_type == "many2one":
319 vevent.add(field).value = tools.ustr(data.get(map_field)[1])
320 elif map_type in ("float", "integer"):
321 vevent.add(field).value = str(data.get(map_field))
322 elif map_type == "selection":
323 if not self.ical_get(field, 'mapping'):
324 vevent.add(field).value = (tools.ustr(data[map_field])).upper()
326 for key1, val1 in self.ical_get(field, 'mapping').items():
327 if val1 == data[map_field]:
328 vevent.add(field).value = key1
331 def check_import(self, cr, uid, vals, context={}):
333 @param self: The object pointer
334 @param cr: the current row, from the database cursor,
335 @param uid: the current user’s ID for security checks,
336 @param vals: Get Values
337 @param context: A standard dictionary for contextual values
341 model_obj = self.pool.get(context.get('model'))
345 exists, r_id = uid2openobjectid(cr, val['id'], context.get('model'), \
346 val.get('recurrent_id'))
347 if val.has_key('create_date'): val.pop('create_date')
348 u_id = val.get('id', None)
351 val.update({'recurrent_uid': exists})
352 model_obj.write(cr, uid, [r_id], val)
355 model_obj.write(cr, uid, [exists], val)
358 if u_id in recur_pool and val.get('recurrent_id'):
359 val.update({'recurrent_uid': recur_pool[u_id]})
360 revent_id = model_obj.create(cr, uid, val)
361 ids.append(revent_id)
363 event_id = model_obj.create(cr, uid, val)
364 recur_pool[u_id] = event_id
367 raise osv.except_osv(('Error !'), (str(e)))
370 def export_cal(self, cr, uid, datas, vobj=None, context={}):
372 @param self: The object pointer
373 @param cr: the current row, from the database cursor,
374 @param uid: the current user’s ID for security checks,
375 @param datas: Get Data's for caldav
376 @param context: A standard dictionary for contextual values
380 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
381 ical = vobject.iCalendar()
382 self.create_ics(cr, uid, datas, vobj, ical, context=context)
385 raise osv.except_osv(('Error !'), (str(e)))
387 def import_cal(self, cr, uid, content, data_id=None, 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 data_id: Get Data’s ID or False
393 @param context: A standard dictionary for contextual values
396 ical_data = base64.decodestring(content)
397 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, context)
398 parsedCal = vobject.readOne(ical_data)
401 for child in parsedCal.getChildren():
402 if child.name.lower() in ('vevent', 'vtodo'):
403 vals = self.parse_ics(cr, uid, child, context=context)
407 if vals: res.append(vals)
408 self.ical_reset('value')
411 class Calendar(CalDAV, osv.osv):
412 _name = 'basic.calendar'
413 _calname = 'calendar'
416 'prodid': None, # Use: R-1, Type: TEXT, Specifies the identifier for the product that created the iCalendar object.
417 'version': None, # Use: R-1, Type: TEXT, Specifies the identifier corresponding to the highest version number
418 # or the minimum and maximum range of the iCalendar specification
419 # that is required in order to interpret the iCalendar object.
420 'calscale': None, # Use: O-1, Type: TEXT, Defines the calendar scale used for the calendar information specified in the iCalendar object.
421 'method': None, # Use: O-1, Type: TEXT, Defines the iCalendar object method associated with the calendar object.
422 'vevent': None, # Use: O-n, Type: Collection of Event class
423 'vtodo': None, # Use: O-n, Type: Collection of ToDo class
424 'vjournal': None, # Use: O-n, Type: Collection of Journal class
425 'vfreebusy': None, # Use: O-n, Type: Collection of FreeBusy class
426 'vtimezone': None, # Use: O-n, Type: Collection of Timezone class
429 'name': fields.char("Name", size=64),
430 'user_id': fields.many2one('res.users', 'Owner'),
431 'collection_id': fields.many2one('document.directory', 'Collection', \
433 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
434 string="Type", size=64),
435 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
436 'create_date': fields.datetime('Created Date'),
437 'write_date': fields.datetime('Modifided Date'),
440 def get_calendar_object(self, cr, uid, uri, context=None):
445 name, file_type = tuple(uri[0].split('.'))
446 res = self.name_search(cr, uid, name)
449 calendar_id, calendar_name = res[0]
450 calendar = self.browse(cr, uid, calendar_id)
451 return node_calendar(uri, context, calendar)
453 def export_cal(self, cr, uid, ids, vobj='vevent', context={}):
455 @param self: The object pointer
456 @param cr: the current row, from the database cursor,
457 @param uid: the current user’s ID for security checks,
458 @param ids: List of calendar’s IDs
459 @param context: A standard dictionary for contextual values
462 cal = self.browse(cr, uid, ids[0])
463 ical = vobject.iCalendar()
464 for line in cal.line_ids:
465 if line.name in ('valarm', 'attendee'):
467 mod_obj = self.pool.get(line.object_id.model)
468 data_ids = mod_obj.search(cr, uid, eval(line.domain), context=context)
469 datas = mod_obj.read(cr, uid, data_ids, context=context)
470 context.update({'model': line.object_id.model,
471 'calendar_id': cal.id
473 self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context)
474 self.create_ics(cr, uid, datas, line.name, ical, context=context)
475 return ical.serialize()
477 def import_cal(self, cr, uid, content, data_id=None, context=None):
479 @param self: The object pointer
480 @param cr: the current row, from the database cursor,
481 @param uid: the current user’s ID for security checks,
482 @param data_id: Get Data’s ID or False
483 @param context: A standard dictionary for contextual values
489 ical_data = base64.decodestring(content)
490 parsedCal = vobject.readOne(ical_data)
492 data_id = self.search(cr, uid, [])[0]
493 cal = self.browse(cr, uid, data_id)
496 for line in cal.line_ids:
497 cal_children[line.name] = line.object_id.model
498 for child in parsedCal.getChildren():
499 if child.name.lower() in cal_children:
500 context.update({'model': cal_children[child.name.lower()],
501 'calendar_id': cal.id
503 self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context)
504 val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
506 obj = self.pool.get(cal_children[child.name.lower()])
507 if hasattr(obj, 'check_import'):
508 obj.check_import(cr, uid, vals, context=context)
510 self.check_import(cr, uid, vals, context=context)
515 class basic_calendar_line(osv.osv):
516 """ Calendar Lines """
518 _name = 'basic.calendar.lines'
519 _description = 'Calendar Lines'
522 'name': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
523 ('valarm', 'Alarm'), \
524 ('attendee', 'Attendee')], \
525 string="Type", size=64),
526 'object_id': fields.many2one('ir.model', 'Object'),
527 'calendar_id': fields.many2one('basic.calendar', 'Calendar', \
528 required=True, ondelete='cascade'),
529 'domain': fields.char('Domain', size=124),
530 'mapping_ids': fields.one2many('basic.calendar.fields', 'type_id', 'Fields Mapping')
534 'domain': lambda *a: '[]',
537 def create(self, cr, uid, vals, context={}):
538 """ create calendar's line
539 @param self: The object pointer
540 @param cr: the current row, from the database cursor,
541 @param uid: the current user’s ID for security checks,
542 @param vals: Get the Values
543 @param context: A standard dictionary for contextual values
546 cr.execute("Select count(id) from basic_calendar_lines \
547 where name='%s' and calendar_id=%s" % (vals.get('name'), vals.get('calendar_id')))
551 raise osv.except_osv(_('Warning !'), _('Can not create \
552 line "%s" more than once' % (vals.get('name'))))
553 return super(basic_calendar_line, self).create(cr, uid, vals, context=context)
555 basic_calendar_line()
558 class basic_calendar_attribute(osv.osv):
559 _name = 'basic.calendar.attributes'
560 _description = 'Calendar attributes'
562 'name': fields.char("Name", size=64, required=True),
563 'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO'), \
564 ('alarm', 'Alarm'), \
565 ('attendee', 'Attendee')], \
566 string="Type", size=64, required=True),
569 basic_calendar_attribute()
572 class basic_calendar_fields(osv.osv):
573 """ Calendar fields """
575 _name = 'basic.calendar.fields'
576 _description = 'Calendar fields'
579 'field_id': fields.many2one('ir.model.fields', 'OpenObject Field'),
580 'name': fields.many2one('basic.calendar.attributes', 'Name', required=True),
581 'type_id': fields.many2one('basic.calendar.lines', 'Type', \
582 required=True, ondelete='cascade'),
583 'expr': fields.char("Expression", size=64),
584 'fn': fields.selection([('field', 'Use the field'),
585 ('const', 'Expression as constant'),
586 ('hours', 'Interval in hours'),
588 'mapping': fields.text('Mapping'),
592 'fn': lambda *a: 'field',
595 def check_line(self, cr, uid, vals, name, context=None):
596 """ check calendar's line
597 @param self: The object pointer
598 @param cr: the current row, from the database cursor,
599 @param uid: the current user’s ID for security checks,
600 @param vals: Get Values
601 @param context: A standard dictionary for contextual values
603 f_obj = self.pool.get('ir.model.fields')
604 field = f_obj.browse(cr, uid, vals['field_id'], context=context)
605 relation = field.relation
606 line_obj = self.pool.get('basic.calendar.lines')
607 l_id = line_obj.search(cr, uid, [('name', '=', name)])
609 line = line_obj.browse(cr, uid, l_id, context=context)[0]
610 line_rel = line.object_id.model
611 if (relation != 'NULL') and (not relation == line_rel):
612 raise osv.except_osv(_('Warning !'), _('Please provide proper configuration of "%s" in Calendar Lines' % (name)))
615 def create(self, cr, uid, vals, context={}):
616 """ Create Calendar's fields
617 @param self: The object pointer
618 @param cr: the current row, from the database cursor,
619 @param uid: the current user’s ID for security checks,
620 @param vals: Get Values
621 @param context: A standard dictionary for contextual values
624 cr.execute('select name from basic_calendar_attributes \
625 where id=%s' % (vals.get('name')))
628 if name in ('valarm', 'attendee'):
629 self.check_line(cr, uid, vals, name, context=context)
630 cr.execute("Select count(id) from basic_calendar_fields \
631 where name=%s and type_id=%s" % (vals.get('name'), vals.get('type_id')))
635 raise osv.except_osv(_('Warning !'), _('Can not map the field more than once'))
636 return super(basic_calendar_fields, self).create(cr, uid, vals, context=context)
638 def write(self, cr, uid, ids, vals, context=None):
639 """ write Calendar's fields
640 @param self: The object pointer
641 @param cr: the current row, from the database cursor,
642 @param uid: the current user’s ID for security checks,
643 @param vals: Get Values
644 @param context: A standard dictionary for contextual values
650 field = self.browse(cr, uid, id, context=context)
651 name = field.name.name
652 if name in ('valarm', 'attendee'):
653 self.check_line(cr, uid, vals, name, context=context)
654 qry = "Select count(id) from basic_calendar_fields \
655 where name=%s and type_id=%s" % (field.name.id, field.type_id.id)
660 raise osv.except_osv(_('Warning !'), _('Can not map same field more than once'))
661 return super(basic_calendar_fields, self).write(cr, uid, ids, vals, context)
663 basic_calendar_fields()
666 class Event(CalDAV, osv.osv_memory):
667 _name = 'basic.calendar.event'
670 'class': None, # Use: O-1, Type: TEXT, Defines the access classification for a calendar component like "PUBLIC" / "PRIVATE" / "CONFIDENTIAL"
671 '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.
672 'description': None, # Use: O-1, Type: TEXT, Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property.
673 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
674 'geo': None, # Use: O-1, Type: FLOAT, Specifies information related to the global position for the activity specified by a calendar component.
675 '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.
676 'location': None, # Use: O-1, Type: TEXT Defines the intended venue for the activity defined by a calendar component.
677 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
678 'priority': None, # Use: O-1, Type: INTEGER, Defines the relative priority for a calendar component.
679 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
680 'seq': None, # Use: O-1, Type: INTEGER, Defines the revision sequence number of the calendar component within a sequence of revision.
681 'status': None, # Use: O-1, Type: TEXT, Defines the overall status or confirmation for the calendar component.
682 'summary': None, # Use: O-1, Type: TEXT, Defines a short summary or subject for the calendar component.
683 'transp': None, # Use: O-1, Type: TEXT, Defines whether an event is transparent or not to busy time searches.
684 'uid': None, # Use: O-1, Type: TEXT, Defines the persistent, globally unique identifier for the calendar component.
685 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
687 'attach': None, # Use: O-n, Type: BINARY, Provides the capability to associate a document object with a calendar component.
688 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
689 'categories': None, # Use: O-n, Type: TEXT, Defines the categories for a calendar component.
690 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
691 'contact': None, # Use: O-n, Type: TEXT, Used to represent contact information or alternately a reference to contact information associated with the calendar component.
692 'exdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/time exceptions for a recurring calendar component.
693 'exrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for an exception to a recurrence set.
695 'related': None, # Use: O-n, Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
696 # like A trigger set 5 minutes after the end of the event or to-do.---> TRIGGER;related=END:PT5M
697 '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
698 'rdate': None, # Use: O-n, Type: DATE-TIME, Defines the list of date/times for a recurrence set.
699 'rrule': None, # Use: O-n, Type: RECUR, Defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
701 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
702 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
705 def export_cal(self, cr, uid, datas, vobj='vevent', context={}):
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 datas: Get datas
711 @param context: A standard dictionary for contextual values
714 return super(Event, self).export_cal(cr, uid, datas, 'vevent', context=context)
719 class ToDo(CalDAV, osv.osv_memory):
720 _name = 'basic.calendar.todo'
758 def export_cal(self, cr, uid, datas, vobj='vevent', context={}):
760 @param self: The object pointer
761 @param cr: the current row, from the database cursor,
762 @param uid: the current user’s ID for security checks,
763 @param datas: Get datas
764 @param context: A standard dictionary for contextual values
767 return super(ToDo, self).export_cal(cr, uid, datas, 'vtodo', context=context)
772 class Journal(CalDAV):
777 class FreeBusy(CalDAV):
779 'contact': None, # Use: O-1, Type: Text, Represent contact information or alternately a reference to contact information associated with the calendar component.
780 'dtstart': None, # Use: O-1, Type: DATE-TIME, Specifies when the calendar component begins.
781 'dtend': None, # Use: O-1, Type: DATE-TIME, Specifies the date and time that a calendar component ends.
782 'duration': None, # Use: O-1, Type: DURATION, Specifies a positive duration of time.
783 'dtstamp': None, # Use: O-1, Type: DATE-TIME, Indicates the date/time that the instance of the iCalendar object was created.
784 'organizer': None, # Use: O-1, Type: CAL-ADDRESS, Defines the organizer for a calendar component.
785 'uid': None, # Use: O-1, Type: Text, Defines the persistent, globally unique identifier for the calendar component.
786 'url': None, # Use: O-1, Type: URL, Defines a Uniform Resource Locator (URL) associated with the iCalendar object.
787 'attendee': None, # Use: O-n, Type: CAL-ADDRESS, Defines an "Attendee" within a calendar component.
788 'comment': None, # Use: O-n, Type: TEXT, Specifies non-processing information intended to provide a comment to the calendar user.
789 'freebusy': None, # Use: O-n, Type: PERIOD, Defines one or more free or busy time intervals.
795 class Timezone(CalDAV, osv.osv_memory):
796 _name = 'basic.calendar.timezone'
797 _calname = 'vtimezone'
800 'tzid': {'field': 'tzid'}, # Use: R-1, Type: Text, Specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
801 '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.
802 '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.
803 'standardc': {'tzprop': None}, # Use: R-1,
804 'daylightc': {'tzprop': None}, # Use: R-1,
805 'x-prop': None, # Use: O-n, Type: Text,
808 def get_name_offset(self, cr, uid, tzid, context={}):
809 """ Get Name Offset value
810 @param self: The object pointer
811 @param cr: the current row, from the database cursor,
812 @param uid: the current user’s ID for security checks,
813 @param context: A standard dictionary for contextual values
816 mytz = pytz.timezone(tzid.title())
817 mydt = datetime.now(tz=mytz)
818 offset = mydt.utcoffset()
819 val = offset.days * 24 + float(offset.seconds) / 3600
820 realoffset = '%02d%02d' % (math.floor(abs(val)), \
821 round(abs(val) % 1 + 0.01, 2) * 60)
822 realoffset = (val < 0 and ('-' + realoffset) or ('+' + realoffset))
823 return (mydt.tzname(), realoffset)
825 def export_cal(self, cr, uid, model, tzid, ical, context={}):
827 @param self: The object pointer
828 @param cr: the current row, from the database cursor,
829 @param uid: the current user’s ID for security checks,
830 @param model: Get Model's name
831 @param context: A standard dictionary for contextual values
835 ctx.update({'model': model})
836 cal_tz = ical.add('vtimezone')
837 cal_tz.add('TZID').value = tzid.title()
838 tz_std = cal_tz.add('STANDARD')
839 tzname, offset = self.get_name_offset(cr, uid, tzid)
840 tz_std.add("TZOFFSETFROM").value = offset
841 tz_std.add("TZOFFSETTO").value = offset
842 tz_std.add("DTSTART").value = datetime.now() # TODO
843 tz_std.add("TZNAME").value = tzname
846 def import_cal(self, cr, uid, ical_data, context=None):
848 @param self: The object pointer
849 @param cr: the current row, from the database cursor,
850 @param uid: the current user’s ID for security checks,
851 @param ical_data: Get calendar's data
852 @param context: A standard dictionary for contextual values
855 for child in ical_data.getChildren():
856 if child.name.lower() == 'tzid':
858 self.ical_set(child.name.lower(), tzname, 'value')
859 vals = map_data(cr, uid, self)
865 class Alarm(CalDAV, osv.osv_memory):
866 _name = 'basic.calendar.alarm'
870 'action': None, # Use: R-1, Type: Text, defines the action to be invoked when an alarm is triggered LIKE "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
871 '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
872 'summary': None, # Use: R-1, Type: Text Which contains the text to be used as the message subject. Use for EMAIL
873 '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
874 '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
875 '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
876 '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
877 '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.
881 def export_cal(self, cr, uid, model, alarm_id, vevent, context={}):
883 @param self: The object pointer
884 @param cr: the current row, from the database cursor,
885 @param uid: the current user’s ID for security checks,
886 @param model: Get Model's name
887 @param alarm_id: Get Alarm's Id
888 @param context: A standard dictionary for contextual values
891 valarm = vevent.add('valarm')
892 alarm_object = self.pool.get(model)
893 alarm_data = alarm_object.read(cr, uid, alarm_id, [])
895 # Compute trigger data
896 interval = alarm_data['trigger_interval']
897 occurs = alarm_data['trigger_occurs']
898 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
899 or -(alarm_data['trigger_duration'])
900 related = alarm_data['trigger_related']
901 trigger = valarm.add('TRIGGER')
902 trigger.params['related'] = [related.upper()]
903 if interval == 'days':
904 delta = timedelta(days=duration)
905 if interval == 'hours':
906 delta = timedelta(hours=duration)
907 if interval == 'minutes':
908 delta = timedelta(minutes=duration)
909 trigger.value = delta
911 # Compute other details
912 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
913 valarm.add('ACTION').value = alarm_data['action']
916 def import_cal(self, cr, uid, ical_data, context=None):
918 @param self: The object pointer
919 @param cr: the current row, from the database cursor,
920 @param uid: the current user’s ID for security checks,
921 @param ical_data: Get calendar's Data
922 @param context: A standard dictionary for contextual values
926 ctx.update({'model': context.get('model', None)})
927 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
928 for child in ical_data.getChildren():
929 if child.name.lower() == 'trigger':
930 seconds = child.value.seconds
931 days = child.value.days
932 diff = (days * 86400) + seconds
937 related = days > 0 and 'after' or 'before'
938 elif (abs(diff) / 3600) == 0:
939 duration = abs(diff / 60)
941 related = days >= 0 and 'after' or 'before'
943 duration = abs(diff / 3600)
945 related = days >= 0 and 'after' or 'before'
946 self.ical_set('trigger_interval', interval, 'value')
947 self.ical_set('trigger_duration', duration, 'value')
948 self.ical_set('trigger_occurs', related.lower(), 'value')
950 if child.params.get('related'):
951 self.ical_set('trigger_related', child.params.get('related')[0].lower(), 'value')
953 self.ical_set(child.name.lower(), child.value.lower(), 'value')
954 vals = map_data(cr, uid, self)
960 class Attendee(CalDAV, osv.osv_memory):
961 _name = 'basic.calendar.attendee'
962 _calname = 'attendee'
965 'cutype': None, # Use: 0-1 Specify the type of calendar user specified by the property like "INDIVIDUAL"/"GROUP"/"RESOURCE"/"ROOM"/"UNKNOWN".
966 'member': None, # Use: 0-1 Specify the group or list membership of the calendar user specified by the property.
967 '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"
968 '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".
969 '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.
970 'delegated-to': None, # Use: 0-1 Specify the calendar users to whom the calendar user specified by the property has delegated participation.
971 'delegated-from': None, # Use: 0-1 Specify the calendar users that have delegated their participation to the calendar user specified by the property.
972 'sent-by': None, # Use: 0-1 Specify the calendar user that is acting on behalf of the calendar user specified by the property.
973 'cn': None, # Use: 0-1 Specify the common name to be associated with the calendar user specified by the property.
974 'dir': None, # Use: 0-1 Specify reference to a directory entry associated with the calendar user specified by the property.
975 'language': None, # Use: 0-1 Specify the language for text values in a property or property parameter.
978 def import_cal(self, cr, uid, ical_data, context=None):
980 @param self: The object pointer
981 @param cr: the current row, from the database cursor,
982 @param uid: the current user’s ID for security checks,
983 @param ical_data: Get calendar's Data
984 @param context: A standard dictionary for contextual values
988 ctx.update({'model': context.get('model', None)})
989 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
990 for para in ical_data.params:
991 if para.lower() == 'cn':
992 self.ical_set(para.lower(), ical_data.params[para][0]+':'+ \
993 ical_data.value, 'value')
995 self.ical_set(para.lower(), ical_data.params[para][0].lower(), 'value')
996 if not ical_data.params.get('CN'):
997 self.ical_set('cn', ical_data.value, 'value')
998 vals = map_data(cr, uid, self)
1001 def export_cal(self, cr, uid, model, attendee_ids, vevent, context={}):
1003 @param self: The object pointer
1004 @param cr: the current row, from the database cursor,
1005 @param uid: the current user’s ID for security checks,
1006 @param model: Get model's name
1007 @param attendee_ids: Get Attendee's Id
1008 @param context: A standard dictionary for contextual values
1011 attendee_object = self.pool.get(model)
1012 ctx = context.copy()
1013 ctx.update({'model': model})
1014 self.__attribute__ = get_attribute_mapping(cr, uid, self._calname, ctx)
1015 for attendee in attendee_object.read(cr, uid, attendee_ids, []):
1016 attendee_add = vevent.add('attendee')
1018 for a_key, a_val in self.__attribute__.items():
1019 if attendee[a_val['field']] and a_val['field'] != 'cn':
1020 if a_val['type'] in ('text', 'char', 'selection'):
1021 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1022 elif a_val['type'] == 'boolean':
1023 attendee_add.params[a_key] = [str(attendee[a_val['field']])]
1024 if a_val['field'] == 'cn' and attendee[a_val['field']]:
1025 cn_val = [str(attendee[a_val['field']])]
1027 attendee_add.params['CN'] = cn_val
1028 if not attendee['email']:
1029 raise osv.except_osv(_('Error !'), _('Attendee must have an Email Id'))
1030 elif attendee['email']:
1031 attendee_add.value = 'MAILTO:' + attendee['email']
1036 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: