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 datetime import datetime, timedelta
24 from dateutil import parser
25 from osv import fields, osv
26 from base_calendar import base_calendar
27 from service import web_services
28 from tools.translate import _
35 1:"January", 2:"February", 3:"March", 4:"April", \
36 5:"May", 6:"June", 7:"July", 8:"August", 9:"September", \
37 10:"October", 11:"November", 12:"December"}
39 def caldav_id2real_id(caldav_id=None, with_date=False):
40 if caldav_id and isinstance(caldav_id, (str, unicode)):
41 res = caldav_id.split('-')
45 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
46 time.strptime(res[1], "%Y%m%d%H%M%S"))
47 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
48 end = start + timedelta(hours=with_date)
49 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
51 return caldav_id and int(caldav_id) or caldav_id
53 def real_id2caldav_id(real_id, recurrent_date):
54 if real_id and recurrent_date:
55 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
56 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
57 return '%d-%s' % (real_id, recurrent_date)
61 def _links_get(self, cr, uid, context={}):
62 obj = self.pool.get('res.request.link')
63 ids = obj.search(cr, uid, [])
64 res = obj.read(cr, uid, ids, ['object', 'name'], context)
65 return [(r['object'], r['name']) for r in res]
67 class calendar_attendee(osv.osv):
68 _name = 'calendar.attendee'
69 _description = 'Attendee information'
74 def _get_address(self, name=None, email=None):
77 return (name or '') + (email and ('MAILTO:' + email) or '')
79 def _compute_data(self, cr, uid, ids, name, arg, context):
83 def get_delegate_data(user):
84 email = user.address_id and user.address_id.email or ''
85 return self._get_address(user.name, email)
87 for attdata in self.browse(cr, uid, ids, context=context):
91 if not attdata.sent_by_uid:
95 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
96 attdata.sent_by_uid.address_id.email)
99 result[id][name] = self._get_address(attdata.user_id.name, attdata.email)
100 elif attdata.partner_address_id:
101 result[id][name] = self._get_address(attdata.partner_id.name, attdata.email)
103 result[id][name] = self._get_address(None, attdata.email)
104 if name == 'delegated_to':
105 user_obj = self.pool.get('res.users')
106 todata = map(get_delegate_data, attdata.del_to_user_ids)
107 result[id][name] = ', '.join(todata)
108 if name == 'delegated_from':
110 user_obj = self.pool.get('res.users')
111 fromdata = map(get_delegate_data, attdata.del_from_user_ids)
112 result[id][name] = ', '.join(fromdata)
113 if name == 'event_date':
114 # TO fix date for project task
116 model, res_id = tuple(attdata.ref.split(','))
117 model_obj = self.pool.get(model)
118 obj = model_obj.read(cr, uid, res_id, ['date'])[0]
119 result[id][name] = None#obj['date']
121 result[id][name] = None
122 if name == 'event_end_date':
124 model, res_id = tuple(attdata.ref.split(','))
125 model_obj = self.pool.get(model)
126 obj = model_obj.read(cr, uid, res_id, ['date_deadline'])[0]
127 result[id][name] = obj['date_deadline']
129 result[id][name] = None
130 if name == 'sent_by_uid':
132 model, res_id = tuple(attdata.ref.split(','))
133 model_obj = self.pool.get(model)
134 obj = model_obj.read(cr, uid, res_id, ['user_id'])[0]
135 result[id][name] = obj['user_id']
137 result[id][name] = uid
140 def _links_get(self, cr, uid, context={}):
141 obj = self.pool.get('res.request.link')
142 ids = obj.search(cr, uid, [])
143 res = obj.read(cr, uid, ids, ['object', 'name'], context)
144 return [(r['object'], r['name']) for r in res]
146 def _lang_get(self, cr, uid, context={}):
147 obj = self.pool.get('res.lang')
148 ids = obj.search(cr, uid, [])
149 res = obj.read(cr, uid, ids, ['code', 'name'], context)
150 res = [((r['code']).replace('_', '-'), r['name']) for r in res]
154 'cutype': fields.selection([('individual', 'Individual'), \
155 ('group', 'Group'), ('resource', 'Resource'), \
156 ('room', 'Room'), ('unknown', 'Unknown') ], \
157 'User Type', help="Specify the type of calendar user"),
158 'member': fields.char('Member', size=124,
159 help="Indicate the groups that the attendee belongs to"),
160 'role': fields.selection([('req-participant', 'Participation required'), \
161 ('chair', 'Chair Person'), \
162 ('opt-participant', 'Optional Participation'), \
163 ('non-participant', 'For information Purpose')], 'Role', \
164 help='Participation role for the calendar user'),
165 'state': fields.selection([('tentative', 'Tentative'),
166 ('needs-action', 'Needs Action'),
167 ('accepted', 'Accepted'),
168 ('declined', 'Declined'),
169 ('delegated', 'Delegated')], 'State', readonly=True,
170 help="Status of the attendee's participation"),
171 'rsvp': fields.boolean('Required Reply?',
172 help="Indicats whether the favor of a reply is requested"),
173 'delegated_to': fields.function(_compute_data, method=True, \
174 string='Delegated To', type="char", size=124, store=True, \
175 multi='delegated_to', help="The users that the original \
176 request was delegated to"),
177 'del_to_user_ids': fields.many2many('res.users', 'att_del_to_user_rel',
178 'attendee_id', 'user_id', 'Users'),
179 'delegated_from': fields.function(_compute_data, method=True, string=\
180 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
181 'del_from_user_ids': fields.many2many('res.users', 'att_del_from_user_rel', \
182 'attendee_id', 'user_id', 'Users'),
183 'sent_by': fields.function(_compute_data, method=True, string='Sent By', type="char", multi='sent_by', store=True, size=124, help="Specify the user that is acting on behalf of the calendar user"),
184 'sent_by_uid': fields.many2one('res.users', 'Sent by User'),
185 'cn': fields.function(_compute_data, method=True, string='Common name', type="char", size=124, multi='cn', store=True),
186 'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."),
187 'language': fields.selection(_lang_get, 'Language',
188 help="To specify the language for text values in a property or property parameter."),
189 'user_id': fields.many2one('res.users', 'User'),
190 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
191 'partner_id':fields.related('partner_address_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
192 'email': fields.char('Email', size=124),
193 'event_date': fields.function(_compute_data, method=True, string='Event Date', type="datetime", multi='event_date'),
194 'event_end_date': fields.function(_compute_data, method=True, string='Event End Date', type="datetime", multi='event_end_date'),
195 'ref': fields.reference('Document Ref', selection=_links_get, size=128),
196 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
199 'state': lambda *x: 'needs-action',
202 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
204 return {'value': {'email': ''}}
205 usr_obj = self.pool.get('res.users')
206 user = usr_obj.browse(cr, uid, user_id, *args)
207 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
209 def do_tentative(self, cr, uid, ids, context=None, *args):
210 self.write(cr, uid, ids, {'state': 'tentative'}, context)
212 def do_accept(self, cr, uid, ids, context=None, *args):
213 self.write(cr, uid, ids, {'state': 'accepted'}, context)
215 def do_decline(self, cr, uid, ids, context=None, *args):
216 self.write(cr, uid, ids, {'state': 'declined'}, context)
218 def create(self, cr, uid, vals, context={}):
219 if not vals.get("email") and vals.get("cn"):
220 cnval = vals.get("cn").split(':')
221 email = filter(lambda x:x.__contains__('@'), cnval)
222 vals['email'] = email[0]
223 vals['cn'] = vals.get("cn")
224 res = super(calendar_attendee, self).create(cr, uid, vals, context)
229 class res_alarm(osv.osv):
231 _description = 'Basic Alarm Information'
233 'name':fields.char('Name', size=256, required=True),
234 'trigger_occurs': fields.selection([('before', 'Before'), ('after', 'After')], \
235 'Triggers', required=True),
236 'trigger_interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), \
237 ('days', 'Days')], 'Interval', required=True),
238 'trigger_duration': fields.integer('Duration', required=True),
239 'trigger_related': fields.selection([('start', 'The event starts'), ('end', \
240 'The event ends')], 'Related to', required=True),
241 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
242 are both optional, but if one occurs, so MUST the other"""),
243 'repeat': fields.integer('Repeat'),
244 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the event alarm information without removing it."),
249 'trigger_interval': lambda *x: 'minutes',
250 'trigger_duration': lambda *x: 5,
251 'trigger_occurs': lambda *x: 'before',
252 'trigger_related': lambda *x: 'start',
253 'active': lambda *x: 1,
256 def do_alarm_create(self, cr, uid, ids, model, date, context={}):
257 alarm_obj = self.pool.get('calendar.alarm')
258 ir_obj = self.pool.get('ir.model')
259 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
261 model_obj = self.pool.get(model)
262 for data in model_obj.browse(cr, uid, ids):
263 basic_alarm = data.alarm_id
264 if not context.get('alarm_id'):
265 self.do_alarm_unlink(cr, uid, [data.id], model)
267 self.do_alarm_unlink(cr, uid, [data.id], model)
271 'description': data.description,
273 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
274 'trigger_related': basic_alarm.trigger_related,
275 'trigger_duration': basic_alarm.trigger_duration,
276 'trigger_occurs': basic_alarm.trigger_occurs,
277 'trigger_interval': basic_alarm.trigger_interval,
278 'duration': basic_alarm.duration,
279 'repeat': basic_alarm.repeat,
281 'event_date': data[date],
283 'model_id': model_id,
286 alarm_id = alarm_obj.create(cr, uid, vals)
287 cr.execute('Update %s set caldav_alarm_id=%s, alarm_id=%s \
288 where id=%s' % (model_obj._table, \
289 alarm_id, basic_alarm.id, data.id))
293 def do_alarm_unlink(self, cr, uid, ids, model, context={}):
294 alarm_obj = self.pool.get('calendar.alarm')
295 ir_obj = self.pool.get('ir.model')
296 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
297 model_obj = self.pool.get(model)
298 for datas in model_obj.browse(cr, uid, ids):
299 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
300 if alarm_ids and len(alarm_ids):
301 alarm_obj.unlink(cr, uid, alarm_ids)
302 cr.execute('Update %s set caldav_alarm_id=NULL, alarm_id=NULL\
303 where id=%s' % (model_obj._table, datas.id))
309 class calendar_alarm(osv.osv):
310 _name = 'calendar.alarm'
311 _description = 'Event alarm information'
312 _inherit = 'res.alarm'
316 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
317 'name': fields.char('Summary', size=124, help="""Contains the text to be used as the message subject for email
318 or contains the text to be used for display"""),
319 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
320 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
321 required=True, help="Defines the action to be invoked when an alarm is triggered"),
322 'description': fields.text('Description', help='Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property'),
323 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
324 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
325 'attach': fields.binary('Attachment', help="""* Points to a sound resource, which is rendered when the alarm is triggered for audio,
326 * File which is intended to be sent as message attachments for email,
327 * Points to a procedure resource, which is invoked when the alarm is triggered for procedure."""),
328 'res_id': fields.integer('Resource ID'),
329 'model_id': fields.many2one('ir.model', 'Model'),
330 'user_id': fields.many2one('res.users', 'Owner'),
331 'event_date': fields.datetime('Event Date'),
332 'event_end_date': fields.datetime('Event End Date'),
333 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
334 'state':fields.selection([
339 ], 'State', select=True, readonly=True),
343 'action': lambda *x: 'email',
344 'state': lambda *x: 'run',
347 def create(self, cr, uid, vals, context={}):
348 event_date = vals.get('event_date', False)
350 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
351 if vals['trigger_interval'] == 'days':
352 delta = timedelta(days=vals['trigger_duration'])
353 if vals['trigger_interval'] == 'hours':
354 delta = timedelta(hours=vals['trigger_duration'])
355 if vals['trigger_interval'] == 'minutes':
356 delta = timedelta(minutes=vals['trigger_duration'])
357 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
358 vals['trigger_date'] = trigger_date
359 res = super(calendar_alarm, self).create(cr, uid, vals, context)
362 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
366 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
367 cr.execute("select alarm.id as id \
368 from calendar_alarm alarm \
369 where alarm.state = %s and alarm.trigger_date <= %s", ('run', current_datetime))
370 res = cr.dictfetchall()
371 alarm_ids = map(lambda x: x['id'], res)
372 attendee_obj = self.pool.get('calendar.attendee')
373 request_obj = self.pool.get('res.request')
375 for alarm in self.browse(cr, uid, alarm_ids):
376 if alarm.action == 'display':
379 'act_from': alarm.user_id.id,
380 'act_to': alarm.user_id.id,
381 'body': alarm.description,
382 'trigger_date': alarm.trigger_date,
383 'ref_doc1': '%s,%s' % (alarm.model_id.model, alarm.res_id)
385 request_id = request_obj.create(cr, uid, value)
386 request_ids = [request_id]
387 for attendee in alarm.attendee_ids:
388 value['act_to'] = attendee.user_id.id
389 request_id = request_obj.create(cr, uid, value)
390 request_ids.append(request_id)
391 request_obj.request_send(cr, uid, request_ids)
393 if alarm.action == 'email':
394 sub = '[Openobject Remainder] %s' % (alarm.name)
404 """ % (alarm.name, alarm.trigger_date, alarm.description, \
405 alarm.user_id.name, alarm.user_id.sign)
406 mail_to = [alarm.user_id.address_id.email]
407 for att in alarm.attendee_ids:
408 mail_to.append(att.user_id.address_id.email)
411 tools.confirm['from_mail'],
416 self.write(cr, uid, [alarm.id], {'state':'done'})
422 class calendar_event(osv.osv):
423 _name = "calendar.event"
424 _description = "Calendar Event"
427 def onchange_rrule_type(self, cr, uid, ids, rtype, *args, **argv):
428 if rtype == 'none' or not rtype:
429 return {'value': {'rrule': ''}}
430 if rtype == 'custom':
432 rrule = self.pool.get('calendar.custom.rrule')
433 rrulestr = rrule.compute_rule_string(cr, uid, {'freq': rtype.upper(), \
435 return {'value': {'rrule': rrulestr}}
437 def _get_duration(self, cr, uid, ids, name, arg, context):
439 for event in self.browse(cr, uid, ids, context=context):
440 start = datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")
442 if event.date_deadline:
443 end = datetime.strptime(event.date_deadline[:19], "%Y-%m-%d %H:%M:%S")
445 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
446 res[event.id] = round(duration, 2)
449 def _set_duration(self, cr, uid, id, name, value, arg, context):
450 event = self.browse(cr, uid, id, context=context)
451 start = datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")
452 end = start + timedelta(hours=value)
453 cr.execute("UPDATE %s set date_deadline='%s' \
454 where id=%s"% (self._table, end.strftime("%Y-%m-%d %H:%M:%S"), id))
458 'id': fields.integer('ID'),
459 'sequence': fields.integer('Sequence'),
460 'name': fields.char('Description', size=64, required=True),
461 'date': fields.datetime('Date'),
462 'date_deadline': fields.datetime('Deadline'),
463 'create_date': fields.datetime('Created' ,readonly=True),
464 'duration': fields.function(_get_duration, method=True, \
465 fnct_inv=_set_duration, string='Duration'),
466 'description': fields.text('Your action'),
467 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
468 ('confidential', 'Confidential')], 'Mark as'),
469 'location': fields.char('Location', size=264, help="Location of Event"),
470 'show_as': fields.selection([('free', 'Free'), \
473 'caldav_url': fields.char('Caldav URL', size=264),
474 'exdate': fields.text('Exception Date/Times', help="This property \
475 defines the list of date/time exceptions for arecurring calendar component."),
476 'exrule': fields.char('Exception Rule', size=352, help="defines a \
477 rule or repeating pattern for anexception to a recurrence set"),
478 'rrule': fields.char('Recurrent Rule', size=124),
479 'rrule_type': fields.selection([('none', 'None'), ('daily', 'Daily'), \
480 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
481 ('yearly', 'Yearly'), ('custom', 'Custom')], 'Recurrency'),
482 'alarm_id': fields.many2one('res.alarm', 'Alarm'),
483 'caldav_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
484 'recurrent_uid': fields.integer('Recurrent ID'),
485 'recurrent_id': fields.datetime('Recurrent ID date'),
489 'class': lambda *a: 'public',
490 'show_as': lambda *a: 'busy',
493 def export_cal(self, cr, uid, ids, context={}):
494 ids = map(lambda x: caldav_id2real_id(x), ids)
495 event_data = self.read(cr, uid, ids)
496 event_obj = self.pool.get('basic.calendar.event')
497 ical = event_obj.export_cal(cr, uid, event_data, context={'model': self._name})
498 cal_val = ical.serialize()
499 cal_val = cal_val.replace('"', '').strip()
502 def import_cal(self, cr, uid, data, data_id=None, context={}):
503 event_obj = self.pool.get('basic.calendar.event')
504 vals = event_obj.import_cal(cr, uid, data, context=context)
505 return self.check_import(cr, uid, vals, context=context)
507 def check_import(self, cr, uid, vals, context={}):
510 exists, r_id = base_calendar.uid2openobjectid(cr, val['id'], self._name, \
511 val.get('recurrent_id'))
512 if val.has_key('create_date'): val.pop('create_date')
513 val['caldav_url'] = context.get('url') or ''
516 val.update({'recurrent_uid': exists})
517 self.write(cr, uid, [r_id], val)
520 self.write(cr, uid, [exists], val)
523 event_id = self.create(cr, uid, val)
527 def modify_this(self, cr, uid, ids, defaults, context=None, *args):
528 datas = self.read(cr, uid, ids[0], context=context)
529 date = datas.get('date')
531 'recurrent_uid': caldav_id2real_id(datas['id']),
532 'recurrent_id': defaults.get('date'),
533 'rrule_type': 'none',
536 new_id = self.copy(cr, uid, ids[0], default=defaults, context=context)
539 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
542 if isinstance(select, (str, int, long)):
547 if ids and (base_start_date or base_until_date):
548 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, \
549 m.exdate from " + self._table + \
550 " m where m.id in ("\
551 + ','.join(map(lambda x: str(x), ids))+")")
554 for data in cr.dictfetchall():
555 start_date = base_start_date and datetime.strptime(base_start_date, "%Y-%m-%d") or False
556 until_date = base_until_date and datetime.strptime(base_until_date, "%Y-%m-%d") or False
559 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
560 if start_date and start_date <= event_date:
561 start_date = event_date
562 if not data['rrule']:
563 if start_date and (event_date < start_date):
565 if until_date and (event_date > until_date):
567 idval = real_id2caldav_id(data['id'], data['date'])
571 exdate = data['exdate'] and data['exdate'].split(',') or []
572 event_obj = self.pool.get('basic.calendar.event')
573 rrule_str = data['rrule']
575 rrule_until_date = False
577 for rule in rrule_str.split(';'):
578 name, value = rule.split('=')
581 value = parser.parse(value)
582 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
583 if until_date and until_date >= rrule_until_date:
584 until_date = rrule_until_date
586 value = until_date.strftime("%Y%m%d%H%M%S")
587 new_rule = '%s=%s' % (name, value)
588 new_rrule_str.append(new_rule)
589 if not is_until and until_date:
590 value = until_date.strftime("%Y%m%d%H%M%S")
592 new_rule = '%s=%s' % (name, value)
593 new_rrule_str.append(new_rule)
594 new_rrule_str = ';'.join(new_rrule_str)
595 start_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
596 rdates = event_obj.get_recurrent_dates(str(new_rrule_str), exdate, start_date)
598 r_date = datetime.strptime(rdate, "%Y-%m-%d %H:%M:%S")
599 if start_date and r_date < start_date:
601 if until_date and r_date > until_date:
603 idval = real_id2caldav_id(data['id'], rdate)
608 if isinstance(select, (str, int, long)):
609 return ids and ids[0] or False
612 def search(self, cr, uid, args, offset=0, limit=100, order=None,
613 context=None, count=False):
614 args_without_date = []
618 if arg[0] not in ('date', unicode('date')):
619 args_without_date.append(arg)
621 if arg[1] in ('>', '>='):
623 elif arg[1] in ('<', '<='):
625 res = super(calendar_event, self).search(cr, uid, args_without_date, offset,
626 limit, order, context, count)
627 return self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
630 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
633 if isinstance(ids, (str, int, long)):
639 id = caldav_id2real_id(id)
640 if not id in new_ids:
642 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
643 if vals.has_key('alarm_id') or vals.has_key('caldav_alarm_id'):
644 alarm_obj = self.pool.get('res.alarm')
645 context.update({'alarm_id': vals.get('alarm_id')})
646 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
649 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
650 if isinstance(ids, (str, int, long)):
654 select = map(lambda x: caldav_id2real_id(x), select)
655 res = super(calendar_event, self).browse(cr, uid, select, context, list_class, fields_process)
656 if isinstance(ids, (str, int, long)):
657 return res and res[0] or False
660 def read(self, cr, uid, ids, fields=None, context={}, load='_classic_read'):
661 if isinstance(ids, (str, int, long)):
665 select = map(lambda x: (x, caldav_id2real_id(x)), select)
667 if fields and 'date' not in fields:
668 fields.append('date')
669 for caldav_id, real_id in select:
670 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, \
672 ls = caldav_id2real_id(caldav_id, with_date=res.get('duration', 0))
673 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
675 res['date_deadline'] = ls[2]
676 res['id'] = caldav_id
679 if isinstance(ids, (str, int, long)):
680 return result and result[0] or False
683 def copy(self, cr, uid, id, default=None, context={}):
684 res = super(calendar_event, self).copy(cr, uid, caldav_id2real_id(id), default, context)
685 alarm_obj = self.pool.get('res.alarm')
686 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
689 def unlink(self, cr, uid, ids, context=None):
692 ls = caldav_id2real_id(id)
693 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
695 for record in self.read(cr, uid, [caldav_id2real_id(id)], \
696 ['date', 'rrule', 'exdate']):
698 exdate = (record['exdate'] and (record['exdate'] + ',') or '') + \
699 ''.join((re.compile('\d')).findall(date_new)) + 'Z'
700 if record['date'] == date_new:
701 res = self.write(cr, uid, [caldav_id2real_id(id)], {'exdate': exdate})
703 ids = map(lambda x: caldav_id2real_id(x), ids)
704 res = super(calendar_event, self).unlink(cr, uid, caldav_id2real_id(ids))
705 alarm_obj = self.pool.get('res.alarm')
706 alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
708 ids = map(lambda x: caldav_id2real_id(x), ids)
709 res = super(calendar_event, self).unlink(cr, uid, ids)
710 alarm_obj = self.pool.get('res.alarm')
711 alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
714 def create(self, cr, uid, vals, context={}):
715 res = super(calendar_event, self).create(cr, uid, vals, context)
716 alarm_obj = self.pool.get('res.alarm')
717 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
722 class calendar_todo(osv.osv):
723 _name = "calendar.todo"
724 _inherit = "calendar.event"
725 _description = "Calendar Task"
727 def _get_date(self, cr, uid, ids, name, arg, context):
729 for event in self.browse(cr, uid, ids, context=context):
730 res[event.id] = event.date_start
733 def _set_date(self, cr, uid, id, name, value, arg, context):
734 event = self.browse(cr, uid, id, context=context)
735 cr.execute("UPDATE %s set date_start='%s' where id=%s" \
736 % (self._table, value, id))
740 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
741 string='Duration', store=True, type='datetime'),
742 'duration': fields.integer('Duration'),
747 def import_cal(self, cr, uid, data, data_id=None, context={}):
748 todo_obj = self.pool.get('basic.calendar.todo')
749 vals = todo_obj.import_cal(cr, uid, data, context=context)
750 return self.check_import(cr, uid, vals, context=context)
752 def check_import(self, cr, uid, vals, context={}):
755 obj_tm = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.project_time_mode_id
756 if not val.has_key('planned_hours'):
757 # 'Computes duration' in days
759 if val.get('date') and val.get('date_deadline'):
760 start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
761 end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
763 plan = (diff.seconds/float(86400) + diff.days) * obj_tm.factor
764 val['planned_hours'] = plan
766 # Converts timedelta into hours
767 hours = (val['planned_hours'].seconds / float(3600)) + \
768 (val['planned_hours'].days * 24)
769 val['planned_hours'] = hours
770 exists, r_id = base_calendar.uid2openobjectid(cr, val['id'], self._name, val.get('recurrent_id'))
773 self.write(cr, uid, [exists], val)
776 task_id = self.create(cr, uid, val)
780 def export_cal(self, cr, uid, ids, context={}):
781 task_datas = self.read(cr, uid, ids, [], context ={'read': True})
783 for task in task_datas:
784 if task.get('planned_hours', None) and task.get('date_deadline', None):
785 task.pop('planned_hours')
787 todo_obj = self.pool.get('basic.calendar.todo')
788 ical = todo_obj.export_cal(cr, uid, tasks, context={'model': self._name})
789 calendar_val = ical.serialize()
790 calendar_val = calendar_val.replace('"', '').strip()
795 class ir_attachment(osv.osv):
796 _name = 'ir.attachment'
797 _inherit = 'ir.attachment'
799 def search_count(self, cr, user, args, context=None):
802 args1.append(map(lambda x:str(x).split('-')[0], arg))
803 return super(ir_attachment, self).search_count(cr, user, args1, context)
805 def search(self, cr, uid, args, offset=0, limit=None, order=None,
806 context=None, count=False):
808 for i, arg in enumerate(new_args):
809 if arg[0] == 'res_id':
810 new_args[i] = (arg[0], arg[1], caldav_id2real_id(arg[2]))
811 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
812 limit=limit, order=order,
813 context=context, count=False)
816 class ir_values(osv.osv):
817 _inherit = 'ir.values'
819 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
820 isobject=False, meta=False, preserve_user=False, company=False):
823 if type(data) in (list, tuple):
824 new_model.append((data[0], caldav_id2real_id(data[1])))
826 new_model.append(data)
827 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
828 value, replace, isobject, meta, preserve_user, company)
830 def get(self, cr, uid, key, key2, models, meta=False, context={}, \
831 res_id_req=False, without_user=True, key2_req=True):
834 if type(data) in (list, tuple):
835 new_model.append((data[0], caldav_id2real_id(data[1])))
837 new_model.append(data)
838 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
839 meta, context, res_id_req, without_user, key2_req)
843 class ir_model(osv.osv):
845 _inherit = 'ir.model'
847 def read(self, cr, uid, ids, fields=None, context={},
848 load='_classic_read'):
849 data = super(ir_model, self).read(cr, uid, ids, fields=fields, \
850 context=context, load=load)
853 val['id'] = caldav_id2real_id(val['id'])
858 class virtual_report_spool(web_services.report_spool):
860 def exp_report(self, db, uid, object, ids, datas=None, context=None):
861 if object == 'printscreen.list':
862 return super(virtual_report_spool, self).exp_report(db, uid, \
863 object, ids, datas, context)
866 new_ids.append(caldav_id2real_id(id))
867 datas['id'] = caldav_id2real_id(datas['id'])
868 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
870 virtual_report_spool()
872 class calendar_custom_rrule(osv.osv):
873 _name = "calendar.custom.rrule"
874 _description = "Custom Recurrency Rule"
877 'freq': fields.selection([('None', 'No Repeat'), \
878 ('secondly', 'Secondly'), \
879 ('minutely', 'Minutely'), \
880 ('hourly', 'Hourly'), \
881 ('daily', 'Daily'), \
882 ('weekly', 'Weekly'), \
883 ('monthly', 'Monthly'), \
884 ('yearly', 'Yearly')], 'Frequency', required=True),
885 'interval': fields.integer('Interval'),
886 'count': fields.integer('Count'),
887 'mo': fields.boolean('Mon'),
888 'tu': fields.boolean('Tue'),
889 'we': fields.boolean('Wed'),
890 'th': fields.boolean('Thu'),
891 'fr': fields.boolean('Fri'),
892 'sa': fields.boolean('Sat'),
893 'su': fields.boolean('Sun'),
894 'select1': fields.selection([('date', 'Date of month'), \
895 ('day', 'Day of month')], 'Option'),
896 'day': fields.integer('Date of month'),
897 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
898 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
899 ('FR', 'Friday'), ('SA', 'Saturday'), \
900 ('SU', 'Sunday')], 'Weekday'),
901 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
902 ('3', 'Third'), ('4', 'Fourth'), \
903 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
904 'month_list': fields.selection(months.items(), 'Month'),
905 'end_date': fields.date('Repeat Until')
909 'freq': lambda *x: 'daily',
910 'select1': lambda *x: 'date',
911 'interval': lambda *x: 1,
914 def compute_rule_string(self, cr, uid, datas, context=None, *args):
915 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
920 # logic for computing rrule string
922 freq = datas.get('freq')
924 obj.write(cr, uid, [res_obj.id], {'rrule': ''})
928 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
930 weekstring = ';BYDAY=' + ','.join(byday)
932 elif freq == 'monthly':
933 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
934 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
935 if datas.get('select1')=='day':
936 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
937 elif datas.get('select1')=='date':
938 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
940 elif freq == 'yearly':
941 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
942 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
943 bymonth = ';BYMONTH=' + str(datas.get('month_list'))
944 if datas.get('select1')=='day':
945 bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
946 elif datas.get('select1')=='date':
947 bystring = ';BYMONTHDAY=' + str(datas.get('day'))
948 yearstring = bymonth + bystring
950 if datas.get('end_date'):
951 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + '235959Z'
952 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
953 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
955 rrule_string = 'FREQ=' + freq.upper() + weekstring + ';INTERVAL=' + \
956 str(datas.get('interval')) + enddate + monthstring + yearstring
961 def do_add(self, cr, uid, ids, context={}):
962 datas = self.read(cr, uid, ids)[0]
963 if datas.get('interval') <= 0:
964 raise osv.except_osv(_('Error!'), ("Please select proper Interval"))
967 if not context or not context.get('model'):
970 model = context.get('model')
971 obj = self.pool.get(model)
972 res_obj = obj.browse(cr, uid, context['active_id'])
974 rrule_string = self.compute_rule_string(cr, uid, datas)
975 obj.write(cr, uid, [res_obj.id], {'rrule': rrule_string})
978 calendar_custom_rrule()
980 class res_users(osv.osv):
981 _inherit = 'res.users'
983 def _get_user_avail(self, cr, uid, ids, context=None):
984 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
986 attendee_obj = self.pool.get('calendar.attendee')
987 attendee_ids = attendee_obj.search(cr, uid, [
988 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
989 ('state', '=', 'accepted'), ('user_id', 'in', ids)
992 result = cr.dictfetchall()
993 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
994 user_id = attendee_data['user_id']
996 res.update({user_id:status})
998 #TOCHECK: Delegrated Event
999 #cr.execute("SELECT user_id,'busy' FROM att_del_to_user_rel where user_id = ANY(%s)", (ids,))
1000 #res.update(cr.dictfetchall())
1002 if user_id not in res:
1003 res[user_id] = 'free'
1007 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1008 return self._get_user_avail(cr, uid, ids, context=context)
1011 'availability': fields.function(_get_user_avail_fun, type='selection', \
1012 selection=[('free', 'Free'), ('busy', 'Busy')], \
1013 string='Free/Busy', method=True),
1017 class invite_attendee_wizard(osv.osv_memory):
1018 _name = "caldav.invite.attendee"
1019 _description = "Invite Attendees"
1022 'type': fields.selection([('internal', 'Internal User'), \
1023 ('external', 'External Email'), \
1024 ('partner', 'Partner Contacts')], 'Type', required=True),
1025 'user_ids': fields.many2many('res.users', 'invite_user_rel',
1026 'invite_id', 'user_id', 'Users'),
1027 'partner_id': fields.many2one('res.partner', 'Partner'),
1028 'email': fields.char('Email', size=124),
1029 'contact_ids': fields.many2many('res.partner.address', 'invite_contact_rel',
1030 'invite_id', 'contact_id', 'Contacts'),
1033 def do_invite(self, cr, uid, ids, context={}):
1034 datas = self.read(cr, uid, ids)[0]
1035 if not context or not context.get('model'):
1038 model = context.get('model')
1039 obj = self.pool.get(model)
1040 res_obj = obj.browse(cr, uid, context['active_id'])
1041 type = datas.get('type')
1042 att_obj = self.pool.get('calendar.attendee')
1043 vals = {'ref': '%s,%s' % (model, caldav_id2real_id(context['active_id']))}
1044 if type == 'internal':
1045 user_obj = self.pool.get('res.users')
1046 for user_id in datas.get('user_ids', []):
1047 user = user_obj.browse(cr, uid, user_id)
1048 if not user.address_id.email:
1049 raise osv.except_osv(_('Error!'), \
1050 ("User does not have an email Address"))
1051 vals.update({'user_id': user_id,
1052 'email': user.address_id.email})
1053 att_id = att_obj.create(cr, uid, vals)
1054 obj.write(cr, uid, res_obj.id, {'attendee_ids': [(4, att_id)]})
1056 elif type == 'external' and datas.get('email'):
1057 vals.update({'email': datas['email']})
1058 att_id = att_obj.create(cr, uid, vals)
1059 obj.write(cr, uid, res_obj.id, {'attendee_ids': [(4, att_id)]})
1060 elif type == 'partner':
1061 add_obj = self.pool.get('res.partner.address')
1062 for contact in add_obj.browse(cr, uid, datas['contact_ids']):
1064 'partner_address_id': contact.id,
1065 'email': contact.email})
1066 att_id = att_obj.create(cr, uid, vals)
1067 obj.write(cr, uid, res_obj.id, {'attendee_ids': [(4, att_id)]})
1071 def onchange_partner_id(self, cr, uid, ids, partner_id, *args, **argv):
1073 return {'value': {'contact_ids': []}}
1074 cr.execute('select id from res_partner_address \
1075 where partner_id=%s' % (partner_id))
1076 contacts = map(lambda x: x[0], cr.fetchall())
1078 raise osv.except_osv(_('Error!'), \
1079 ("Partner does not have any Contacts"))
1081 return {'value': {'contact_ids': contacts}}
1083 invite_attendee_wizard()
1086 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: