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, date
23 from dateutil import parser
24 from dateutil import rrule
25 from dateutil.relativedelta import relativedelta
26 from osv import fields, osv
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"
40 def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
42 Get recurrent dates based on Rule string considering exdate and start date
43 @param rrulestring: Rulestring
44 @param exdate: List of exception dates for rrule
45 @param startdate: Startdate for computing recurrent dates
46 @return: List of Recurrent dates
49 val = parser.parse(''.join((re.compile('\d')).findall(date)))
53 startdate = datetime.now()
58 rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
60 datetime_obj = todate(date)
61 rset1._exdate.append(datetime_obj)
64 rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
68 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
70 This function converts virtual event id into real id of actual event
71 @param base_calendar_id: Id of calendar
72 @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
75 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
76 res = base_calendar_id.split('-')
81 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
82 time.strptime(res[1], "%Y%m%d%H%M%S"))
83 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
84 end = start + timedelta(hours=with_date)
85 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
88 return base_calendar_id and int(base_calendar_id) or base_calendar_id
90 def real_id2base_calendar_id(real_id, recurrent_date):
92 Convert real id of record into virtual id using recurrent_date
93 e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
95 @return: real id with recurrent date.
98 if real_id and recurrent_date:
99 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
100 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
101 return '%d-%s' % (real_id, recurrent_date)
104 def _links_get(self, cr, uid, context=None):
107 @param cr: the current row, from the database cursor,
108 @param uid: the current user’s ID for security checks,
109 @param context: A standard dictionary for contextual values
110 @return: list of dictionary which contain object and name and id.
112 obj = self.pool.get('res.request.link')
113 ids = obj.search(cr, uid, [])
114 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
115 return [(r['object'], r['name']) for r in res]
117 html_invitation = """
120 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
121 <title>%(name)s</title>
124 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
125 style="font-family: Arial, Sans-serif; font-size: 14">
127 <td width="100%%">Hello,</td>
130 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
133 <td width="100%%">Below are the details of event:</td>
137 <table cellspacing="0" cellpadding="5" border="0" summary=""
138 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
139 <tr valign="center" align="center">
140 <td bgcolor="DFDFDF">
146 <table cellpadding="8" cellspacing="0" border="0"
147 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
151 <div><b>Start Date</b></div>
154 <td>%(start_date)s</td>
156 <div><b>End Date</b></div>
159 <td width="25%%">%(end_date)s</td>
162 <td><b>Description</b></td>
164 <td colspan="3">%(description)s</td>
168 <div><b>Location</b></div>
171 <td colspan="3">%(location)s</td>
175 <div><b>Event Attendees</b></div>
180 <div>%(attendees)s</div>
188 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
189 style="font-family: Arial, Sans-serif; font-size: 14">
191 <td width="100%%">From:</td>
194 <td width="100%%">%(user)s</td>
197 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
200 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
207 class calendar_attendee(osv.osv):
209 Calendar Attendee Information
211 _name = 'calendar.attendee'
212 _description = 'Attendee information'
217 def _get_address(self, name=None, email=None):
219 Gives email information in ical CAL-ADDRESS type format
220 @param name: Name for CAL-ADDRESS value
221 @param email: Email address for CAL-ADDRESS value
225 return (name or '') + (email and ('MAILTO:' + email) or '')
227 def _compute_data(self, cr, uid, ids, name, arg, context=None):
229 Compute data on function fields for attendee values .
230 @param cr: the current row, from the database cursor,
231 @param uid: the current user’s ID for security checks,
232 @param ids: List of calendar attendee’s IDs.
233 @param name: name of field.
234 @param context: A standard dictionary for contextual values
235 @return: Dictionary of form {id: {'field Name': value'}}.
239 for attdata in self.browse(cr, uid, ids, context=context):
242 if name == 'sent_by':
243 if not attdata.sent_by_uid:
244 result[id][name] = ''
247 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
248 attdata.sent_by_uid.user_email)
252 result[id][name] = attdata.user_id.name
253 elif attdata.partner_id:
254 result[id][name] = attdata.partner_id.name or False
256 result[id][name] = attdata.email or ''
258 if name == 'delegated_to':
260 for child in attdata.child_ids:
262 todata.append('MAILTO:' + child.email)
263 result[id][name] = ', '.join(todata)
265 if name == 'delegated_from':
267 for parent in attdata.parent_ids:
269 fromdata.append('MAILTO:' + parent.email)
270 result[id][name] = ', '.join(fromdata)
272 if name == 'event_date':
274 result[id][name] = attdata.ref.date
276 result[id][name] = False
278 if name == 'event_end_date':
280 result[id][name] = attdata.ref.date_deadline
282 result[id][name] = False
284 if name == 'sent_by_uid':
286 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
288 result[id][name] = uid
290 if name == 'language':
291 user_obj = self.pool.get('res.users')
292 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
293 result[id][name] = lang.replace('_', '-')
297 def _links_get(self, cr, uid, context=None):
299 Get request link for ref field in calendar attendee.
300 @param cr: the current row, from the database cursor,
301 @param uid: the current user’s ID for security checks,
302 @param context: A standard dictionary for contextual values
303 @return: list of dictionary which contain object and name and id.
305 obj = self.pool.get('res.request.link')
306 ids = obj.search(cr, uid, [])
307 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
308 return [(r['object'], r['name']) for r in res]
310 def _lang_get(self, cr, uid, context=None):
312 Get language for language selection field.
313 @param cr: the current row, from the database cursor,
314 @param uid: the current user’s ID for security checks,
315 @param context: A standard dictionary for contextual values
316 @return: list of dictionary which contain code and name and id.
318 obj = self.pool.get('res.lang')
319 ids = obj.search(cr, uid, [])
320 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
321 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
325 'cutype': fields.selection([('individual', 'Individual'), \
326 ('group', 'Group'), ('resource', 'Resource'), \
327 ('room', 'Room'), ('unknown', 'Unknown') ], \
328 'Invite Type', help="Specify the type of Invitation"),
329 'member': fields.char('Member', size=124,
330 help="Indicate the groups that the attendee belongs to"),
331 'role': fields.selection([('req-participant', 'Participation required'), \
332 ('chair', 'Chair Person'), \
333 ('opt-participant', 'Optional Participation'), \
334 ('non-participant', 'For information Purpose')], 'Role', \
335 help='Participation role for the calendar user'),
336 'state': fields.selection([('needs-action', 'Needs Action'),
337 ('tentative', 'Tentative'),
338 ('declined', 'Declined'),
339 ('accepted', 'Accepted'),
340 ('delegated', 'Delegated')], 'Status', readonly=True, \
341 help="Status of the attendee's participation"),
342 'rsvp': fields.boolean('Required Reply?',
343 help="Indicats whether the favor of a reply is requested"),
344 'delegated_to': fields.function(_compute_data, \
345 string='Delegated To', type="char", size=124, store=True, \
346 multi='delegated_to', help="The users that the original \
347 request was delegated to"),
348 'delegated_from': fields.function(_compute_data, string=\
349 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
350 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
351 'attendee_id', 'parent_id', 'Delegrated From'),
352 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
353 'attendee_id', 'child_id', 'Delegrated To'),
354 'sent_by': fields.function(_compute_data, string='Sent By', \
355 type="char", multi='sent_by', store=True, size=124, \
356 help="Specify the user that is acting on behalf of the calendar user"),
357 'sent_by_uid': fields.function(_compute_data, string='Sent By User', \
358 type="many2one", relation="res.users", multi='sent_by_uid'),
359 'cn': fields.function(_compute_data, string='Common name', \
360 type="char", size=124, multi='cn', store=True),
361 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
362 that points to the directory information corresponding to the attendee."),
363 'language': fields.function(_compute_data, string='Language', \
364 type="selection", selection=_lang_get, multi='language', \
365 store=True, help="To specify the language for text values in a\
366 property or property parameter."),
367 'user_id': fields.many2one('res.users', 'User'),
368 'partner_id': fields.many2one('res.partner', 'Contact'),
369 'email': fields.char('Email', size=124, help="Email of Invited Person"),
370 'event_date': fields.function(_compute_data, string='Event Date', \
371 type="datetime", multi='event_date'),
372 'event_end_date': fields.function(_compute_data, \
373 string='Event End Date', type="datetime", \
374 multi='event_end_date'),
375 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
376 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
380 'state': 'needs-action',
381 'role': 'req-participant',
383 'cutype': 'individual',
386 def copy(self, cr, uid, id, default=None, context=None):
387 raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
389 def get_ics_file(self, cr, uid, event_obj, context=None):
391 Returns iCalendar file for the event invitation
392 @param self: The object pointer
393 @param cr: the current row, from the database cursor,
394 @param uid: the current user’s ID for security checks,
395 @param event_obj: Event object (browse record)
396 @param context: A standard dictionary for contextual values
397 @return: .ics file content
400 def ics_datetime(idate, short=False):
402 if short or len(idate)<=10:
403 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
405 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
409 # FIXME: why isn't this in CalDAV?
413 cal = vobject.iCalendar()
414 event = cal.add('vevent')
415 if not event_obj.date_deadline or not event_obj.date:
416 raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
417 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
418 event.add('dtstart').value = ics_datetime(event_obj.date)
419 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
420 event.add('summary').value = event_obj.name
421 if event_obj.description:
422 event.add('description').value = event_obj.description
423 if event_obj.location:
424 event.add('location').value = event_obj.location
426 event.add('rrule').value = event_obj.rrule
427 if event_obj.organizer:
428 event_org = event.add('organizer')
429 event_org.params['CN'] = [event_obj.organizer]
430 event_org.value = 'MAILTO:' + (event_obj.organizer)
431 elif event_obj.user_id or event_obj.organizer_id:
432 event_org = event.add('organizer')
433 organizer = event_obj.organizer_id
435 organizer = event_obj.user_id
436 event_org.params['CN'] = [organizer.name]
437 event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
439 if event_obj.alarm_id:
440 # computes alarm data
441 valarm = event.add('valarm')
442 alarm_object = self.pool.get('res.alarm')
443 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
444 # Compute trigger data
445 interval = alarm_data['trigger_interval']
446 occurs = alarm_data['trigger_occurs']
447 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
448 or -(alarm_data['trigger_duration'])
449 related = alarm_data['trigger_related']
450 trigger = valarm.add('TRIGGER')
451 trigger.params['related'] = [related.upper()]
452 if interval == 'days':
453 delta = timedelta(days=duration)
454 if interval == 'hours':
455 delta = timedelta(hours=duration)
456 if interval == 'minutes':
457 delta = timedelta(minutes=duration)
458 trigger.value = delta
459 # Compute other details
460 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
462 for attendee in event_obj.attendee_ids:
463 attendee_add = event.add('attendee')
464 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
465 attendee_add.params['ROLE'] = [str(attendee.role)]
466 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
467 attendee_add.value = 'MAILTO:' + (attendee.email or '')
468 res = cal.serialize()
471 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
473 Send mail for event invitation to event attendees.
474 @param cr: the current row, from the database cursor,
475 @param uid: the current user’s ID for security checks,
476 @param ids: List of attendee’s IDs.
477 @param email_from: Email address for user sending the mail
478 @param context: A standard dictionary for contextual values
484 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
485 mail_message = self.pool.get('mail.message')
486 for att in self.browse(cr, uid, ids, context=context):
487 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
488 sign = '<br>'.join(sign and sign.split('\n') or [])
493 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
495 for att2 in self.browse(cr, uid, other_invitation_ids):
496 att_infos.append(((att2.user_id and att2.user_id.name) or \
497 (att2.partner_id and att2.partner_id.name) or \
498 att2.email) + ' - Status: ' + att2.state.title())
499 body_vals = {'name': res_obj.name,
500 'start_date': res_obj.date,
501 'end_date': res_obj.date_deadline or False,
502 'description': res_obj.description or '-',
503 'location': res_obj.location or '-',
504 'attendees': '<br>'.join(att_infos),
505 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
509 body = html_invitation % body_vals
510 if mail_to and email_from:
511 attach = self.get_ics_file(cr, uid, res_obj, context=context)
512 mail_message.schedule_with_attach(cr, uid,
517 attachments=attach and {'invitation.ics': attach} or None,
524 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
526 Make entry on email and availbility on change of user_id field.
527 @param cr: the current row, from the database cursor,
528 @param uid: the current user’s ID for security checks,
529 @param ids: List of calendar attendee’s IDs.
530 @param user_id: Changed value of User id
531 @return: dictionary of value. which put value in email and availability fields.
535 return {'value': {'email': ''}}
536 usr_obj = self.pool.get('res.users')
537 user = usr_obj.browse(cr, uid, user_id, *args)
538 return {'value': {'email': user.user_email, 'availability':user.availability}}
540 def do_tentative(self, cr, uid, ids, context=None, *args):
541 """ Makes event invitation as Tentative
542 @param self: The object pointer
543 @param cr: the current row, from the database cursor,
544 @param uid: the current user’s ID for security checks,
545 @param ids: List of calendar attendee’s IDs
546 @param *args: Get Tupple value
547 @param context: A standard dictionary for contextual values
549 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
551 def do_accept(self, cr, uid, ids, context=None, *args):
553 Update state of invitation as Accepted and
554 if the invited user is other then event user it will make a copy of this event for invited user
555 @param cr: the current row, from the database cursor,
556 @param uid: the current user’s ID for security checks,
557 @param ids: List of calendar attendee’s IDs.
558 @param context: A standard dictionary for contextual values
564 for vals in self.browse(cr, uid, ids, context=context):
565 if vals.ref and vals.ref.user_id:
566 mod_obj = self.pool.get(vals.ref._name)
567 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
568 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
569 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
573 def do_decline(self, cr, uid, ids, context=None, *args):
574 """ Marks event invitation as Declined
575 @param self: The object pointer
576 @param cr: the current row, from the database cursor,
577 @param uid: the current user’s ID for security checks,
578 @param ids: List of calendar attendee’s IDs
579 @param *args: Get Tupple value
580 @param context: A standard dictionary for contextual values """
583 return self.write(cr, uid, ids, {'state': 'declined'}, context)
585 def create(self, cr, uid, vals, context=None):
586 """ Overrides orm create method.
587 @param self: The object pointer
588 @param cr: the current row, from the database cursor,
589 @param uid: the current user’s ID for security checks,
590 @param vals: Get Values
591 @param context: A standard dictionary for contextual values """
595 if not vals.get("email") and vals.get("cn"):
596 cnval = vals.get("cn").split(':')
597 email = filter(lambda x:x.__contains__('@'), cnval)
598 vals['email'] = email and email[0] or ''
599 vals['cn'] = vals.get("cn")
600 res = super(calendar_attendee, self).create(cr, uid, vals, context)
604 class res_alarm(osv.osv):
605 """Resource Alarm """
607 _description = 'Basic Alarm Information'
610 'name':fields.char('Name', size=256, required=True),
611 'trigger_occurs': fields.selection([('before', 'Before'), \
612 ('after', 'After')], \
613 'Triggers', required=True),
614 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
615 ('hours', 'Hours'), \
616 ('days', 'Days')], 'Interval', \
618 'trigger_duration': fields.integer('Duration', required=True),
619 'trigger_related': fields.selection([('start', 'The event starts'), \
620 ('end', 'The event ends')], \
621 'Related to', required=True),
622 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
623 are both optional, but if one occurs, so MUST the other"""),
624 'repeat': fields.integer('Repeat'),
625 'active': fields.boolean('Active', help="If the active field is set to \
626 true, it will allow you to hide the event alarm information without removing it.")
629 'trigger_interval': 'minutes',
630 'trigger_duration': 5,
631 'trigger_occurs': 'before',
632 'trigger_related': 'start',
636 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
638 Create Alarm for event.
639 @param cr: the current row, from the database cursor,
640 @param uid: the current user’s ID for security checks,
641 @param ids: List of res alarm’s IDs.
642 @param model: Model name.
643 @param date: Event date
644 @param context: A standard dictionary for contextual values
649 alarm_obj = self.pool.get('calendar.alarm')
650 res_alarm_obj = self.pool.get('res.alarm')
651 ir_obj = self.pool.get('ir.model')
652 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
654 model_obj = self.pool.get(model)
655 for data in model_obj.browse(cr, uid, ids, context=context):
657 basic_alarm = data.alarm_id
658 cal_alarm = data.base_calendar_alarm_id
659 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
661 # Find for existing res.alarm
662 duration = cal_alarm.trigger_duration
663 interval = cal_alarm.trigger_interval
664 occurs = cal_alarm.trigger_occurs
665 related = cal_alarm.trigger_related
666 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
667 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
670 'trigger_duration': duration,
671 'trigger_interval': interval,
672 'trigger_occurs': occurs,
673 'trigger_related': related,
674 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
676 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
678 new_res_alarm = alarm_ids[0]
679 cr.execute('UPDATE %s ' % model_obj._table + \
680 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
682 (cal_alarm.id, new_res_alarm, data.id))
684 self.do_alarm_unlink(cr, uid, [data.id], model)
688 'description': data.description,
690 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
691 'trigger_related': basic_alarm.trigger_related,
692 'trigger_duration': basic_alarm.trigger_duration,
693 'trigger_occurs': basic_alarm.trigger_occurs,
694 'trigger_interval': basic_alarm.trigger_interval,
695 'duration': basic_alarm.duration,
696 'repeat': basic_alarm.repeat,
698 'event_date': data[date],
700 'model_id': model_id,
703 alarm_id = alarm_obj.create(cr, uid, vals)
704 cr.execute('UPDATE %s ' % model_obj._table + \
705 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
707 ( alarm_id, basic_alarm.id, data.id) )
710 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
712 Delete alarm specified in ids
713 @param cr: the current row, from the database cursor,
714 @param uid: the current user’s ID for security checks,
715 @param ids: List of res alarm’s IDs.
716 @param model: Model name for which alarm is to be cleared.
721 alarm_obj = self.pool.get('calendar.alarm')
722 ir_obj = self.pool.get('ir.model')
723 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
724 model_obj = self.pool.get(model)
725 for datas in model_obj.browse(cr, uid, ids, context=context):
726 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
728 alarm_obj.unlink(cr, uid, alarm_ids)
729 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
730 where id=%%s' % model_obj._table,(datas.id,))
735 class calendar_alarm(osv.osv):
736 _name = 'calendar.alarm'
737 _description = 'Event alarm information'
738 _inherit = 'res.alarm'
742 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
743 'name': fields.char('Summary', size=124, help="""Contains the text to be \
744 used as the message subject for email \
745 or contains the text to be used for display"""),
746 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
747 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
748 required=True, help="Defines the action to be invoked when an alarm is triggered"),
749 'description': fields.text('Description', help='Provides a more complete \
750 description of the calendar component, than that \
751 provided by the "SUMMARY" property'),
752 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
753 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
754 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
755 which is rendered when the alarm is triggered for audio,
756 * File which is intended to be sent as message attachments for email,
757 * Points to a procedure resource, which is invoked when\
758 the alarm is triggered for procedure."""),
759 'res_id': fields.integer('Resource ID'),
760 'model_id': fields.many2one('ir.model', 'Model'),
761 'user_id': fields.many2one('res.users', 'Owner'),
762 'event_date': fields.datetime('Event Date'),
763 'event_end_date': fields.datetime('Event End Date'),
764 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
765 'state':fields.selection([
770 ], 'Status', select=True, readonly=True),
778 def create(self, cr, uid, vals, context=None):
780 Overrides orm create method.
781 @param self: The object pointer
782 @param cr: the current row, from the database cursor,
783 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
784 @param context: A standard dictionary for contextual values
785 @return: new record id for calendar_alarm.
789 event_date = vals.get('event_date', False)
791 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
792 if vals['trigger_interval'] == 'days':
793 delta = timedelta(days=vals['trigger_duration'])
794 if vals['trigger_interval'] == 'hours':
795 delta = timedelta(hours=vals['trigger_duration'])
796 if vals['trigger_interval'] == 'minutes':
797 delta = timedelta(minutes=vals['trigger_duration'])
798 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
799 vals['trigger_date'] = trigger_date
800 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
803 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
805 """Scheduler for event reminder
806 @param self: The object pointer
807 @param cr: the current row, from the database cursor,
808 @param uid: the current user’s ID for security checks,
809 @param ids: List of calendar alarm’s IDs.
810 @param use_new_cursor: False or the dbname
811 @param context: A standard dictionary for contextual values
815 mail_message = self.pool.get('mail.message')
816 current_datetime = datetime.now()
817 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
821 for alarm in self.browse(cr, uid, alarm_ids, context=context):
822 next_trigger_date = None
824 model_obj = self.pool.get(alarm.model_id.model)
825 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
829 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
830 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
832 trigger_interval = alarm.trigger_interval
833 if trigger_interval == 'days':
834 delta = timedelta(days=alarm.trigger_duration)
835 if trigger_interval == 'hours':
836 delta = timedelta(hours=alarm.trigger_duration)
837 if trigger_interval == 'minutes':
838 delta = timedelta(minutes=alarm.trigger_duration)
839 delta = alarm.trigger_occurs == 'after' and delta or -delta
841 for rdate in recurrent_dates:
842 if rdate + delta > current_datetime:
844 if rdate + delta <= current_datetime:
845 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
846 rest_dates = recurrent_dates[len(re_dates):]
847 next_trigger_date = rest_dates and rest_dates[0] or None
850 re_dates = [alarm.trigger_date]
852 for r_date in re_dates:
853 ref = alarm.model_id.model + ',' + str(alarm.res_id)
855 # search for alreay sent requests
856 #if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
859 # Deactivated because of the removing of res.request
860 # TODO: when cleaning calendar module, re-add this in a new mechanism
861 #if alarm.action == 'display':
864 #'act_from': alarm.user_id.id,
865 #'act_to': alarm.user_id.id,
866 #'body': alarm.description,
867 #'trigger_date': r_date,
870 #request_id = request_obj.create(cr, uid, value)
871 #request_ids = [request_id]
872 #for attendee in res_obj.attendee_ids:
873 #if attendee.user_id:
874 #value['act_to'] = attendee.user_id.id
875 #request_id = request_obj.create(cr, uid, value)
876 #request_ids.append(request_id)
877 #request_obj.request_send(cr, uid, request_ids)
879 if alarm.action == 'email':
880 sub = '[Openobject Reminder] %s' % (alarm.name)
892 """ % (alarm.name, alarm.trigger_date, alarm.description, \
893 alarm.user_id.name, alarm.user_id.signature)
894 mail_to = [alarm.user_id.user_email]
895 for att in alarm.attendee_ids:
896 mail_to.append(att.user_id.user_email)
898 mail_message.schedule_with_attach(cr, uid,
899 tools.config.get('email_from', False),
905 if next_trigger_date:
906 update_vals.update({'trigger_date': next_trigger_date})
908 update_vals.update({'state': 'done'})
909 self.write(cr, uid, [alarm.id], update_vals)
915 class calendar_event(osv.osv):
916 _name = "calendar.event"
917 _description = "Calendar Event"
920 def _tz_get(self, cr, uid, context=None):
921 return [(x.lower(), x) for x in pytz.all_timezones]
923 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
924 """Returns duration and/or end date based on values passed
925 @param self: The object pointer
926 @param cr: the current row, from the database cursor,
927 @param uid: the current user’s ID for security checks,
928 @param ids: List of calendar event’s IDs.
929 @param start_date: Starting date
930 @param duration: Duration between start date and end date
931 @param end_date: Ending Datee
932 @param context: A standard dictionary for contextual values
940 if not end_date and not duration:
942 value['duration'] = duration
944 if allday: # For all day event
945 value = {'duration': 24.0}
948 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
949 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
950 value['date'] = start_date
953 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
954 if end_date and not duration:
955 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
957 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
958 value['duration'] = round(duration, 2)
960 end = start + timedelta(hours=duration)
961 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
962 elif end_date and duration and not allday:
963 # we have both, keep them synchronized:
964 # set duration based on end_date (arbitrary decision: this avoid
965 # getting dates like 06:31:48 instead of 06:32:00)
966 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
968 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
969 value['duration'] = round(duration, 2)
971 return {'value': value}
973 def unlink_events(self, cr, uid, ids, context=None):
975 This function deletes event which are linked with the event with recurrent_uid
976 (Removes the events which refers to the same UID value)
981 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
982 r_ids = map(lambda x: x[0], cr.fetchall())
983 self.unlink(cr, uid, r_ids, context=context)
986 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
988 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
989 @param self: The object pointer
990 @param cr: the current row, from the database cursor,
991 @param id: List of calendar event's ids.
992 @param context: A standard dictionary for contextual values
993 @return: dictionary of rrule value.
997 if not isinstance(ids, list):
1000 for datas in self.read(cr, uid, ids, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'select1', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'exrule', 'day', 'week_list' ], context=context):
1002 if datas.get('interval', 0) < 0:
1003 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative'))
1004 if datas.get('count', 0) < 0:
1005 raise osv.except_osv(_('Warning!'), _('Count cannot be negative'))
1006 if datas['recurrency']:
1007 result[event] = self.compute_rule_string(datas)
1012 def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
1013 data = self._get_empty_rrule_data()
1015 data['recurrency'] = True
1016 for event in self.browse(cr, uid, ids, context=context):
1017 rdate = rule_date or event.date
1018 update_data = self._parse_rrule(field_value, dict(data), rdate)
1019 data.update(update_data)
1020 super(calendar_event, obj).write(cr, uid, ids, data, context=context)
1025 'id': fields.integer('ID', readonly=True),
1026 'sequence': fields.integer('Sequence'),
1027 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1028 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1029 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1030 'create_date': fields.datetime('Created', readonly=True),
1031 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1032 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1033 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1034 ('confidential', 'Public for Employees')], 'Mark as', states={'done': [('readonly', True)]}),
1035 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1036 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1037 'Show as', states={'done': [('readonly', True)]}),
1038 'base_calendar_url': fields.char('Caldav URL', size=264),
1039 'state': fields.selection([('tentative', 'Tentative'),
1040 ('cancelled', 'Cancelled'),
1041 ('confirmed', 'Confirmed'),
1042 ], 'Status', readonly=True),
1043 'exdate': fields.text('Exception Date/Times', help="This property \
1044 defines the list of date/time exceptions for a recurring calendar component."),
1045 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1046 rule or repeating pattern of time to exclude from the recurring rule."),
1047 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1048 fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
1049 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1050 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1051 ('yearly', 'Yearly'),],
1052 'Recurrency', states={'done': [('readonly', True)]},
1053 help="Let the event automatically repeat at that interval"),
1054 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1055 help="Set an alarm at this time, before the event occurs" ),
1056 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1057 'recurrent_uid': fields.integer('Recurrent ID'),
1058 'recurrent_id': fields.datetime('Recurrent ID date'),
1059 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1060 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1061 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1062 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1063 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence termination'),
1064 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1065 'count': fields.integer('Repeat', help="Repeat x times"),
1066 'mo': fields.boolean('Mon'),
1067 'tu': fields.boolean('Tue'),
1068 'we': fields.boolean('Wed'),
1069 'th': fields.boolean('Thu'),
1070 'fr': fields.boolean('Fri'),
1071 'sa': fields.boolean('Sat'),
1072 'su': fields.boolean('Sun'),
1073 'select1': fields.selection([('date', 'Date of month'),
1074 ('day', 'Day of month')], 'Option'),
1075 'day': fields.integer('Date of month'),
1076 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1077 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1078 ('FR', 'Friday'), ('SA', 'Saturday'), \
1079 ('SU', 'Sunday')], 'Weekday'),
1080 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1081 ('3', 'Third'), ('4', 'Fourth'), \
1082 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1083 'month_list': fields.selection(months.items(), 'Month'),
1084 'end_date': fields.date('Repeat Until'),
1085 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1086 'event_id', 'attendee_id', 'Attendees'),
1087 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1088 'active': fields.boolean('Active', help="If the active field is set to \
1089 true, it will allow you to hide the event alarm information without removing it."),
1090 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1093 def default_organizer(self, cr, uid, context=None):
1094 user_pool = self.pool.get('res.users')
1095 user = user_pool.browse(cr, uid, uid, context=context)
1098 res += " <%s>" %(user.user_email)
1102 'end_type' : 'count',
1104 'rrule_type' : 'none',
1105 'state': 'tentative',
1111 'user_id': lambda self, cr, uid, ctx: uid,
1112 'organizer': default_organizer,
1115 def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None):
1116 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1117 This method gives ids of dates that comes between start date and end date of calendar views
1118 @param self: The object pointer
1119 @param cr: the current row, from the database cursor,
1120 @param uid: the current user’s ID for security checks,
1121 @param limit: The Number of Results to Return """
1126 for data in super(calendar_event, self).read(cr, uid, select, context=context):
1127 if not data['rrule']:
1128 result.append(data['id'])
1130 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1131 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1133 if not data['rrule']:
1136 exdate = data['exdate'] and data['exdate'].split(',') or []
1137 rrule_str = data['rrule']
1139 rrule_until_date = False
1141 for rule in rrule_str.split(';'):
1142 name, value = rule.split('=')
1145 value = parser.parse(value)
1146 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1147 value = value.strftime("%Y%m%d%H%M%S")
1148 new_rule = '%s=%s' % (name, value)
1149 new_rrule_str.append(new_rule)
1150 new_rrule_str = ';'.join(new_rrule_str)
1151 rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
1152 for r_date in rdates:
1155 if arg[0] in ('date', 'date_deadline'):
1157 ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
1159 ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
1161 ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
1163 ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
1165 ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
1168 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1169 result.append(idval)
1171 if isinstance(select, (str, int, long)):
1172 return ids and ids[0] or False
1174 ids = list(set(result))
1177 def compute_rule_string(self, datas):
1179 Compute rule string according to value type RECUR of iCalendar from the values given.
1180 @param self: the object pointer
1181 @param datas: dictionary of freq and interval value.
1183 def get_week_string(freq, datas):
1184 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1185 if freq == 'weekly':
1186 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1188 return ';BYDAY=' + ','.join(byday)
1191 def get_month_string(freq, datas):
1192 if freq == 'monthly':
1193 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1194 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1196 if datas.get('select1')=='day':
1197 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1198 elif datas.get('select1')=='date':
1199 return ';BYMONTHDAY=' + str(datas.get('day'))
1202 def get_end_date(datas):
1203 if datas.get('end_date'):
1204 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1206 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1207 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1209 freq=datas.get('rrule_type')
1213 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1215 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1217 def _get_empty_rrule_data(self):
1220 'recurrency' : False,
1222 'rrule_type' : False,
1239 #def _write_rrule(self, cr, uid, ids, field_value, rule_date=False, context=None):
1240 # data = self._get_empty_rrule_data()
1243 # data['recurrency'] = True
1244 # for event in self.browse(cr, uid, ids, context=context):
1245 # rdate = rule_date or event.date
1246 # update_data = self._parse_rrule(field_value, dict(data), rdate)
1247 # data.update(update_data)
1249 # self.write(cr, uid, event.id, data, context=context)
1252 def _parse_rrule(self, rule, data, date_start):
1253 day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1254 rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1255 r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1257 if r._freq > 0 and r._freq < 4:
1258 data['rrule_type'] = rrule_type[r._freq]
1260 data['count'] = r._count
1261 data['interval'] = r._interval
1262 data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1265 for i in xrange(0,7):
1266 if i in r._byweekday:
1267 data[day_list[i]] = True
1268 data['rrule_type'] = 'weekly'
1269 #repeat monthly bynweekday ((weekday, weeknumber), )
1271 data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1272 data['byday'] = r._bynweekday[0][1]
1273 data['select1'] = 'day'
1274 data['rrule_type'] = 'monthly'
1277 data['day'] = r._bymonthday[0]
1278 data['select1'] = 'date'
1279 data['rrule_type'] = 'monthly'
1281 #yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1283 data['interval'] = data['interval'] * 12
1285 #FIXEME handle forever case
1287 #in case of repeat for ever that we do not support right now
1288 if not (data.get('count') or data.get('end_date')):
1290 if data.get('count'):
1291 data['end_type'] = 'count'
1293 data['end_type'] = 'end_date'
1296 def remove_virtual_id(self, ids):
1297 if isinstance(ids, (str, int, long)):
1298 return base_calendar_id2real_id(ids)
1300 if isinstance(ids, (list, tuple)):
1303 res.append(base_calendar_id2real_id(id))
1306 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1307 context = context or {}
1308 args_without_date = []
1313 new_id = self.remove_virtual_id(arg[2])
1314 new_arg = (arg[0], arg[1], new_id)
1315 args_without_date.append(new_arg)
1316 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1317 args_without_date.append(arg)
1319 if context.get('virtual_id', True):
1320 args_without_date.append('|')
1321 args_without_date.append(arg)
1322 if context.get('virtual_id', True):
1323 args_without_date.append(('recurrency','=',1))
1324 filter_date.append(arg)
1326 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1327 0, 0, order, context, count=False)
1328 if context.get('virtual_id', True):
1329 res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
1334 return res[offset:offset+limit]
1338 def _get_data(self, cr, uid, id, context=None):
1339 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1342 def need_to_update(self, event_id, vals):
1343 split_id = str(event_id).split("-")
1344 if len(split_id) < 2:
1347 date_start = vals.get('date', '')
1349 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1350 return date_start == split_id[1]
1355 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1356 context = context or {}
1357 if isinstance(ids, (str, int, long)):
1361 # Special write of complex IDS
1362 for event_id in ids[:]:
1363 if len(str(event_id).split('-')) == 1:
1365 ids.remove(event_id)
1366 real_event_id = base_calendar_id2real_id(event_id)
1367 if not vals.get('recurrency', True):
1368 ids.append(real_event_id)
1371 #if edit one instance of a reccurrent id
1372 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1373 'rrule', 'duration', 'exdate'])
1374 if data.get('rrule'):
1377 'recurrent_uid': real_event_id,
1378 'recurrent_id': data.get('date'),
1379 'rrule_type': 'none',
1381 'recurrency' : False,
1384 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1386 date_new = event_id.split('-')[1]
1387 date_new = time.strftime("%Y%m%dT%H%M%S", \
1388 time.strptime(date_new, "%Y%m%d%H%M%S"))
1389 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1390 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1392 context.update({'active_id': new_id, 'active_ids': [new_id]})
1395 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1396 vals['vtimezone'] = vals['vtimezone'][40:]
1398 res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
1400 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1401 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1402 alarm_obj = self.pool.get('res.alarm')
1403 alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context)
1404 return res or True and False
1406 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1410 if 'date' in groupby:
1411 raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1412 virtual_id = context.get('virtual_id', True)
1413 context.update({'virtual_id': False})
1414 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1416 #remove the count, since the value is not consistent with the result of the search when expand the group
1417 for groupname in groupby:
1418 if re.get(groupname + "_count"):
1419 del re[groupname + "_count"]
1420 re.get('__context', {}).update({'virtual_id' : virtual_id})
1423 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1424 # FIXME This whole id mangling has to go!
1427 fields2 = fields and fields[:] or None
1429 EXTRAFIELDS = ('class','user_id','duration')
1430 for f in EXTRAFIELDS:
1431 if fields and (f not in fields):
1434 if isinstance(ids, (str, int, long)):
1438 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1441 real_data = super(calendar_event, self).read(cr, uid,
1442 [real_id for base_calendar_id, real_id in select],
1443 fields=fields2, context=context, load=load)
1444 real_data = dict(zip([x['id'] for x in real_data], real_data))
1446 for base_calendar_id, real_id in select:
1447 res = real_data[real_id].copy()
1448 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1449 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1451 res['date_deadline'] = ls[2]
1452 res['id'] = base_calendar_id
1458 user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id']
1461 if r['class']=='private':
1463 if f not in ('id','date','date_deadline','duration','user_id','state'):
1469 for k in EXTRAFIELDS:
1470 if (k in r) and ((not fields) or (k not in fields)):
1472 if isinstance(ids, (str, int, long)):
1473 return result and result[0] or False
1476 def copy(self, cr, uid, id, default=None, context=None):
1480 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1481 alarm_obj = self.pool.get('res.alarm')
1482 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1485 def unlink(self, cr, uid, ids, context=None):
1486 if not isinstance(ids, list):
1489 attendee_obj=self.pool.get('calendar.attendee')
1490 for event_id in ids[:]:
1491 if len(str(event_id).split('-')) == 1:
1494 real_event_id = base_calendar_id2real_id(event_id)
1495 data = self.read(cr, uid, real_event_id, ['exdate'], context=context)
1496 date_new = event_id.split('-')[1]
1497 date_new = time.strftime("%Y%m%dT%H%M%S", \
1498 time.strptime(date_new, "%Y%m%d%H%M%S"))
1499 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1500 self.write(cr, uid, [real_event_id], {'exdate': exdate})
1501 ids.remove(event_id)
1502 for event in self.browse(cr, uid, ids, context=context):
1503 if event.attendee_ids:
1504 attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context)
1506 res = super(calendar_event, self).unlink(cr, uid, ids, context=context)
1507 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name)
1508 self.unlink_events(cr, uid, ids, context=context)
1512 def create(self, cr, uid, vals, context=None):
1516 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1517 vals['vtimezone'] = vals['vtimezone'][40:]
1519 #updated_vals = self.onchange_dates(cr, uid, [],
1520 # vals.get('date', False),
1521 # vals.get('duration', False),
1522 # vals.get('date_deadline', False),
1523 # vals.get('allday', False),
1525 #vals.update(updated_vals.get('value', {}))
1527 res = super(calendar_event, self).create(cr, uid, vals, context)
1528 alarm_obj = self.pool.get('res.alarm')
1529 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1532 def do_tentative(self, cr, uid, ids, context=None, *args):
1533 """ Makes event invitation as Tentative
1534 @param self: The object pointer
1535 @param cr: the current row, from the database cursor,
1536 @param uid: the current user’s ID for security checks,
1537 @param ids: List of Event IDs
1538 @param *args: Get Tupple value
1539 @param context: A standard dictionary for contextual values
1541 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1543 def do_cancel(self, cr, uid, ids, context=None, *args):
1544 """ Makes event invitation as Tentative
1545 @param self: The object pointer
1546 @param cr: the current row, from the database cursor,
1547 @param uid: the current user’s ID for security checks,
1548 @param ids: List of Event IDs
1549 @param *args: Get Tupple value
1550 @param context: A standard dictionary for contextual values
1552 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1554 def do_confirm(self, cr, uid, ids, context=None, *args):
1555 """ Makes event invitation as Tentative
1556 @param self: The object pointer
1557 @param cr: the current row, from the database cursor,
1558 @param uid: the current user’s ID for security checks,
1559 @param ids: List of Event IDs
1560 @param *args: Get Tupple value
1561 @param context: A standard dictionary for contextual values
1563 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1567 class calendar_todo(osv.osv):
1568 """ Calendar Task """
1570 _name = "calendar.todo"
1571 _inherit = "calendar.event"
1572 _description = "Calendar Task"
1574 def _get_date(self, cr, uid, ids, name, arg, context=None):
1577 @param self: The object pointer
1578 @param cr: the current row, from the database cursor,
1579 @param uid: the current user’s ID for security checks,
1580 @param ids: List of calendar todo's IDs.
1581 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1582 @param context: A standard dictionary for contextual values
1586 for event in self.browse(cr, uid, ids, context=context):
1587 res[event.id] = event.date_start
1590 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1593 @param self: The object pointer
1594 @param cr: the current row, from the database cursor,
1595 @param uid: the current user’s ID for security checks,
1596 @param id: calendar's ID.
1597 @param value: Get Value
1598 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1599 @param context: A standard dictionary for contextual values
1602 assert name == 'date'
1603 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1606 'date': fields.function(_get_date, fnct_inv=_set_date, \
1607 string='Duration', store=True, type='datetime'),
1608 'duration': fields.integer('Duration'),
1616 class ir_attachment(osv.osv):
1617 _name = 'ir.attachment'
1618 _inherit = 'ir.attachment'
1620 def search_count(self, cr, user, args, context=None):
1622 for domain_item in args:
1623 if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
1624 new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
1626 new_args.append(domain_item)
1627 return super(ir_attachment, self).search_count(cr, user, new_args, context)
1629 def create(self, cr, uid, vals, context=None):
1631 id = context.get('default_res_id', False)
1632 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1633 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1635 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1636 context=None, count=False):
1638 for domain_item in args:
1639 if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
1640 new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
1642 new_args.append(domain_item)
1643 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1644 limit=limit, order=order, context=context, count=False)
1647 class ir_values(osv.osv):
1648 _inherit = 'ir.values'
1650 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1651 isobject=False, meta=False, preserve_user=False, company=False):
1654 @param self: The object pointer
1655 @param cr: the current row, from the database cursor,
1656 @param uid: the current user’s ID for security checks,
1657 @param model: Get The Model
1662 if type(data) in (list, tuple):
1663 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1665 new_model.append(data)
1666 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1667 value, replace, isobject, meta, preserve_user, company)
1669 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1670 res_id_req=False, without_user=True, key2_req=True):
1673 @param self: The object pointer
1674 @param cr: the current row, from the database cursor,
1675 @param uid: the current user’s ID for security checks,
1676 @param model: Get The Model
1682 if type(data) in (list, tuple):
1683 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1685 new_model.append(data)
1686 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1687 meta, context, res_id_req, without_user, key2_req)
1691 class ir_model(osv.osv):
1693 _inherit = 'ir.model'
1695 def read(self, cr, uid, ids, fields=None, context=None,
1696 load='_classic_read'):
1698 Overrides orm read method.
1699 @param self: The object pointer
1700 @param cr: the current row, from the database cursor,
1701 @param uid: the current user’s ID for security checks,
1702 @param ids: List of IR Model’s IDs.
1703 @param context: A standard dictionary for contextual values
1705 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1708 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1709 context=context, load=load)
1712 val['id'] = base_calendar_id2real_id(val['id'])
1713 return isinstance(ids, (str, int, long)) and data[0] or data
1717 class virtual_report_spool(web_services.report_spool):
1719 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1722 @param self: The object pointer
1723 @param db: get the current database,
1724 @param uid: the current user’s ID for security checks,
1725 @param context: A standard dictionary for contextual values
1728 if object == 'printscreen.list':
1729 return super(virtual_report_spool, self).exp_report(db, uid, \
1730 object, ids, datas, context)
1733 new_ids.append(base_calendar_id2real_id(id))
1734 if datas.get('id', False):
1735 datas['id'] = base_calendar_id2real_id(datas['id'])
1736 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1738 virtual_report_spool()
1740 class res_users(osv.osv):
1741 _inherit = 'res.users'
1743 def _get_user_avail(self, cr, uid, ids, context=None):
1745 Get User Availability
1746 @param self: The object pointer
1747 @param cr: the current row, from the database cursor,
1748 @param uid: the current user’s ID for security checks,
1749 @param ids: List of res user’s IDs.
1750 @param context: A standard dictionary for contextual values
1753 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1755 attendee_obj = self.pool.get('calendar.attendee')
1756 attendee_ids = attendee_obj.search(cr, uid, [
1757 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1758 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1761 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1762 user_id = attendee_data['user_id']
1764 res.update({user_id:status})
1766 #TOCHECK: Delegated Event
1768 if user_id not in res:
1769 res[user_id] = 'free'
1773 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1775 Get User Availability Function
1776 @param self: The object pointer
1777 @param cr: the current row, from the database cursor,
1778 @param uid: the current user’s ID for security checks,
1779 @param ids: List of res user’s IDs.
1780 @param context: A standard dictionary for contextual values
1783 return self._get_user_avail(cr, uid, ids, context=context)
1786 'availability': fields.function(_get_user_avail_fun, type='selection', \
1787 selection=[('free', 'Free'), ('busy', 'Busy')], \
1788 string='Free/Busy'),
1794 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: