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 Convert a "virtual/recurring event id" (type string) into a real event id (type int).
71 E.g. virtual/recurring event id is 4-20091201100000, so it will return 4.
72 @param base_calendar_id: id of calendar
73 @param with_date: if a value is passed to this param it will return dates based on value of withdate + base_calendar_id
74 @return: real event id
76 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
77 res = base_calendar_id.split('-')
82 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
83 time.strptime(res[1], "%Y%m%d%H%M%S"))
84 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
85 end = start + timedelta(hours=with_date)
86 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
89 return base_calendar_id and int(base_calendar_id) or base_calendar_id
91 def real_id2base_calendar_id(real_id, recurrent_date):
93 Convert a real event id (type int) into a "virtual/recurring event id" (type string).
94 E.g. real event id is 1 and recurrent_date is set to 01-12-2009 10:00:00, so
95 it will return 1-20091201100000.
96 @param real_id: real event id
97 @param recurrent_date: real event recurrent date
98 @return: string containing the real id and the recurrent date
100 if real_id and recurrent_date:
101 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
102 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
103 return '%d-%s' % (real_id, recurrent_date)
106 def _links_get(self, cr, uid, context=None):
109 @param cr: the current row, from the database cursor
110 @param uid: the current user's ID for security checks
111 @param context: a standard dictionary for contextual values
112 @return: list of dictionary which contain object and name and id
114 obj = self.pool.get('res.request.link')
115 ids = obj.search(cr, uid, [])
116 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
117 return [(r['object'], r['name']) for r in res]
119 html_invitation = """
122 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
123 <title>%(name)s</title>
126 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
127 style="font-family: Arial, Sans-serif; font-size: 14">
129 <td width="100%%">Hello,</td>
132 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
135 <td width="100%%">Below are the details of event:</td>
139 <table cellspacing="0" cellpadding="5" border="0" summary=""
140 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
141 <tr valign="center" align="center">
142 <td bgcolor="DFDFDF">
148 <table cellpadding="8" cellspacing="0" border="0"
149 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
153 <div><b>Start Date</b></div>
156 <td>%(start_date)s</td>
158 <div><b>End Date</b></div>
161 <td width="25%%">%(end_date)s</td>
164 <td><b>Description</b></td>
166 <td colspan="3">%(description)s</td>
170 <div><b>Location</b></div>
173 <td colspan="3">%(location)s</td>
177 <div><b>Event Attendees</b></div>
182 <div>%(attendees)s</div>
190 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
191 style="font-family: Arial, Sans-serif; font-size: 14">
193 <td width="100%%">From:</td>
196 <td width="100%%">%(user)s</td>
199 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
202 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
209 class calendar_attendee(osv.osv):
211 Calendar Attendee Information
213 _name = 'calendar.attendee'
214 _description = 'Attendee information'
219 def _get_address(self, name=None, email=None):
221 Gives email information in ical CAL-ADDRESS type format.
222 @param name: name for CAL-ADDRESS value
223 @param email: email address for CAL-ADDRESS value
227 return (name or '') + (email and ('MAILTO:' + email) or '')
229 def _compute_data(self, cr, uid, ids, name, arg, context=None):
231 Compute data on function fields for attendee values.
232 @param cr: the current row, from the database cursor
233 @param uid: the current user's ID for security checks
234 @param ids: list of calendar attendee's IDs
235 @param name: name of field
236 @param context: a standard dictionary for contextual values
237 @return: dictionary of form {id: {'field Name': value'}}
241 for attdata in self.browse(cr, uid, ids, context=context):
244 if name == 'sent_by':
245 if not attdata.sent_by_uid:
246 result[id][name] = ''
249 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
250 attdata.sent_by_uid.email)
254 result[id][name] = attdata.user_id.name
255 elif attdata.partner_id:
256 result[id][name] = attdata.partner_id.name or False
258 result[id][name] = attdata.email or ''
260 if name == 'delegated_to':
262 for child in attdata.child_ids:
264 todata.append('MAILTO:' + child.email)
265 result[id][name] = ', '.join(todata)
267 if name == 'delegated_from':
269 for parent in attdata.parent_ids:
271 fromdata.append('MAILTO:' + parent.email)
272 result[id][name] = ', '.join(fromdata)
274 if name == 'event_date':
276 result[id][name] = attdata.ref.date
278 result[id][name] = False
280 if name == 'event_end_date':
282 result[id][name] = attdata.ref.date_deadline
284 result[id][name] = False
286 if name == 'sent_by_uid':
288 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
290 result[id][name] = uid
292 if name == 'language':
293 user_obj = self.pool.get('res.users')
294 lang = user_obj.read(cr, uid, uid, ['lang'], context=context)['lang']
295 result[id][name] = lang.replace('_', '-')
299 def _links_get(self, cr, uid, context=None):
301 Get request link for ref field in calendar attendee.
302 @param cr: the current row, from the database cursor
303 @param uid: the current user's id for security checks
304 @param context: A standard dictionary for contextual values
305 @return: list of dictionary which contain object and name and id
307 obj = self.pool.get('res.request.link')
308 ids = obj.search(cr, uid, [])
309 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
310 return [(r['object'], r['name']) for r in res]
312 def _lang_get(self, cr, uid, context=None):
314 Get language for language selection field.
315 @param cr: the current row, from the database cursor
316 @param uid: the current user's id for security checks
317 @param context: a standard dictionary for contextual values
318 @return: list of dictionary which contain code and name and id
320 obj = self.pool.get('res.lang')
321 ids = obj.search(cr, uid, [])
322 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
323 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
327 'cutype': fields.selection([('individual', 'Individual'), \
328 ('group', 'Group'), ('resource', 'Resource'), \
329 ('room', 'Room'), ('unknown', 'Unknown') ], \
330 'Invite Type', help="Specify the type of Invitation"),
331 'member': fields.char('Member', size=124,
332 help="Indicate the groups that the attendee belongs to"),
333 'role': fields.selection([('req-participant', 'Participation required'), \
334 ('chair', 'Chair Person'), \
335 ('opt-participant', 'Optional Participation'), \
336 ('non-participant', 'For information Purpose')], 'Role', \
337 help='Participation role for the calendar user'),
338 'state': fields.selection([('needs-action', 'Needs Action'),
339 ('tentative', 'Uncertain'),
340 ('declined', 'Declined'),
341 ('accepted', 'Accepted'),
342 ('delegated', 'Delegated')], 'Status', readonly=True, \
343 help="Status of the attendee's participation"),
344 'rsvp': fields.boolean('Required Reply?',
345 help="Indicats whether the favor of a reply is requested"),
346 'delegated_to': fields.function(_compute_data, \
347 string='Delegated To', type="char", size=124, store=True, \
348 multi='delegated_to', help="The users that the original \
349 request was delegated to"),
350 'delegated_from': fields.function(_compute_data, string=\
351 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
352 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
353 'attendee_id', 'parent_id', 'Delegrated From'),
354 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
355 'attendee_id', 'child_id', 'Delegrated To'),
356 'sent_by': fields.function(_compute_data, string='Sent By', \
357 type="char", multi='sent_by', store=True, size=124, \
358 help="Specify the user that is acting on behalf of the calendar user"),
359 'sent_by_uid': fields.function(_compute_data, string='Sent By User', \
360 type="many2one", relation="res.users", multi='sent_by_uid'),
361 'cn': fields.function(_compute_data, string='Common name', \
362 type="char", size=124, multi='cn', store=True),
363 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
364 that points to the directory information corresponding to the attendee."),
365 'language': fields.function(_compute_data, string='Language', \
366 type="selection", selection=_lang_get, multi='language', \
367 store=True, help="To specify the language for text values in a\
368 property or property parameter."),
369 'user_id': fields.many2one('res.users', 'User'),
370 'partner_id': fields.many2one('res.partner', 'Contact'),
371 'email': fields.char('Email', size=124, help="Email of Invited Person"),
372 'event_date': fields.function(_compute_data, string='Event Date', \
373 type="datetime", multi='event_date'),
374 'event_end_date': fields.function(_compute_data, \
375 string='Event End Date', type="datetime", \
376 multi='event_end_date'),
377 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
378 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
381 'state': 'needs-action',
382 'role': 'req-participant',
384 'cutype': 'individual',
387 def copy(self, cr, uid, id, default=None, context=None):
388 raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
390 def get_ics_file(self, cr, uid, event_obj, context=None):
392 Returns iCalendar file for the event invitation.
393 @param self: the object pointer
394 @param cr: the current row, from the database cursor
395 @param uid: the current user's id for security checks
396 @param event_obj: event object (browse record)
397 @param context: a standard dictionary for contextual values
398 @return: .ics file content
401 def ics_datetime(idate, short=False):
403 if short or len(idate)<=10:
404 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
406 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
410 # FIXME: why isn't this in CalDAV?
414 cal = vobject.iCalendar()
415 event = cal.add('vevent')
416 if not event_obj.date_deadline or not event_obj.date:
417 raise osv.except_osv(_('Warning!'),_("First you have to specify the date of the invitation."))
418 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
419 event.add('dtstart').value = ics_datetime(event_obj.date)
420 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
421 event.add('summary').value = event_obj.name
422 if event_obj.description:
423 event.add('description').value = event_obj.description
424 if event_obj.location:
425 event.add('location').value = event_obj.location
427 event.add('rrule').value = event_obj.rrule
428 if event_obj.organizer:
429 event_org = event.add('organizer')
430 event_org.params['CN'] = [event_obj.organizer]
431 event_org.value = 'MAILTO:' + (event_obj.organizer)
432 elif event_obj.user_id or event_obj.organizer_id:
433 event_org = event.add('organizer')
434 organizer = event_obj.organizer_id
436 organizer = event_obj.user_id
437 event_org.params['CN'] = [organizer.name]
438 event_org.value = 'MAILTO:' + (organizer.email or organizer.name)
440 if event_obj.alarm_id:
441 # computes alarm data
442 valarm = event.add('valarm')
443 alarm_object = self.pool.get('res.alarm')
444 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
445 # Compute trigger data
446 interval = alarm_data['trigger_interval']
447 occurs = alarm_data['trigger_occurs']
448 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
449 or -(alarm_data['trigger_duration'])
450 related = alarm_data['trigger_related']
451 trigger = valarm.add('TRIGGER')
452 trigger.params['related'] = [related.upper()]
453 if interval == 'days':
454 delta = timedelta(days=duration)
455 if interval == 'hours':
456 delta = timedelta(hours=duration)
457 if interval == 'minutes':
458 delta = timedelta(minutes=duration)
459 trigger.value = delta
460 # Compute other details
461 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
463 for attendee in event_obj.attendee_ids:
464 attendee_add = event.add('attendee')
465 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
466 attendee_add.params['ROLE'] = [str(attendee.role)]
467 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
468 attendee_add.value = 'MAILTO:' + (attendee.email or '')
469 res = cal.serialize()
472 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
474 Send mail for event invitation to event attendees.
475 @param email_from: email address for user sending the mail
478 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
479 for att in self.browse(cr, uid, ids, context=context):
480 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
481 sign = '<br>'.join(sign and sign.split('\n') or [])
486 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
488 for att2 in self.browse(cr, uid, other_invitation_ids):
489 att_infos.append(((att2.user_id and att2.user_id.name) or \
490 (att2.partner_id and att2.partner_id.name) or \
491 att2.email) + ' - Status: ' + att2.state.title())
492 body_vals = {'name': res_obj.name,
493 'start_date': res_obj.date,
494 'end_date': res_obj.date_deadline or False,
495 'description': res_obj.description or '-',
496 'location': res_obj.location or '-',
497 'attendees': '<br>'.join(att_infos),
498 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
502 body = html_invitation % body_vals
503 if mail_to and email_from:
504 ics_file = self.get_ics_file(cr, uid, res_obj, context=context)
505 vals = {'email_from': email_from,
512 vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics',
513 'datas_fname': 'invitation.ics',
514 'datas': str(ics_file).encode('base64')})]
515 self.pool.get('mail.mail').create(cr, uid, vals, context=context)
518 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
520 Make entry on email and availbility on change of user_id field.
521 @param cr: the current row, from the database cursor
522 @param uid: the current user's ID for security checks
523 @param ids: list of calendar attendee's IDs
524 @param user_id: changed value of User id
525 @return: dictionary of values which put value in email and availability fields
529 return {'value': {'email': ''}}
530 usr_obj = self.pool.get('res.users')
531 user = usr_obj.browse(cr, uid, user_id, *args)
532 return {'value': {'email': user.email, 'availability':user.availability}}
534 def do_tentative(self, cr, uid, ids, context=None, *args):
536 Makes event invitation as Tentative.
537 @param self: the object pointer
538 @param cr: the current row, from the database cursor
539 @param uid: the current user's ID for security checks
540 @param ids: list of calendar attendee's IDs
541 @param *args: get Tupple value
542 @param context: a standard dictionary for contextual values
544 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
546 def do_accept(self, cr, uid, ids, context=None, *args):
548 Update state of invitation as Accepted and if the invited user is other
549 then event user it will make a copy of this event for invited user.
550 @param cr: the current row, from the database cursor
551 @param uid: the current user's ID for security checks
552 @param ids: list of calendar attendee's IDs
553 @param context: a standard dictionary for contextual values
559 for vals in self.browse(cr, uid, ids, context=context):
560 if vals.ref and vals.ref.user_id:
561 mod_obj = self.pool.get(vals.ref._name)
562 res=mod_obj.read(cr,uid,[vals.ref.id],['duration','class'],context)
563 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id,'duration':res[0]['duration'],'class':res[0]['class']}
564 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
565 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
569 def do_decline(self, cr, uid, ids, context=None, *args):
571 Marks event invitation as Declined.
572 @param self: the object pointer
573 @param cr: the current row, from the database cursor
574 @param uid: the current user's ID for security checks
575 @param ids: list of calendar attendee's IDs
576 @param *args: get Tupple value
577 @param context: a standard dictionary for contextual values
581 return self.write(cr, uid, ids, {'state': 'declined'}, context)
583 def create(self, cr, uid, vals, context=None):
585 Overrides orm create method.
586 @param self: The object pointer
587 @param cr: the current row, from the database cursor
588 @param uid: the current user's ID for security checks
589 @param vals: get Values
590 @param context: a standard dictionary for contextual values
594 if not vals.get("email") and vals.get("cn"):
595 cnval = vals.get("cn").split(':')
596 email = filter(lambda x:x.__contains__('@'), cnval)
597 vals['email'] = email and email[0] or ''
598 vals['cn'] = vals.get("cn")
599 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 data in model_obj.browse(cr, uid, ids, context=context):
726 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', data.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,(data.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 current_datetime = datetime.now()
816 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
820 for alarm in self.browse(cr, uid, alarm_ids, context=context):
821 next_trigger_date = None
823 model_obj = self.pool.get(alarm.model_id.model)
824 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
828 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
829 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
831 trigger_interval = alarm.trigger_interval
832 if trigger_interval == 'days':
833 delta = timedelta(days=alarm.trigger_duration)
834 if trigger_interval == 'hours':
835 delta = timedelta(hours=alarm.trigger_duration)
836 if trigger_interval == 'minutes':
837 delta = timedelta(minutes=alarm.trigger_duration)
838 delta = alarm.trigger_occurs == 'after' and delta or -delta
840 for rdate in recurrent_dates:
841 if rdate + delta > current_datetime:
843 if rdate + delta <= current_datetime:
844 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
845 rest_dates = recurrent_dates[len(re_dates):]
846 next_trigger_date = rest_dates and rest_dates[0] or None
849 re_dates = [alarm.trigger_date]
852 if alarm.action == 'email':
853 sub = '[OpenERP Reminder] %s' % (alarm.name)
865 """ % (alarm.name, alarm.trigger_date, alarm.description, \
866 alarm.user_id.name, alarm.user_id.signature)
867 mail_to = [alarm.user_id.email]
868 for att in alarm.attendee_ids:
869 mail_to.append(att.user_id.email)
876 'email_from': tools.config.get('email_from', mail_to),
878 self.pool.get('mail.mail').create(cr, uid, vals, context=context)
879 if next_trigger_date:
880 update_vals.update({'trigger_date': next_trigger_date})
882 update_vals.update({'state': 'done'})
883 self.write(cr, uid, [alarm.id], update_vals)
889 class calendar_event(osv.osv):
890 _name = "calendar.event"
891 _description = "Calendar Event"
894 def _tz_get(self, cr, uid, context=None):
895 return [(x.lower(), x) for x in pytz.all_timezones]
897 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
898 """Returns duration and/or end date based on values passed
899 @param self: The object pointer
900 @param cr: the current row, from the database cursor,
901 @param uid: the current user's ID for security checks,
902 @param ids: List of calendar event's IDs.
903 @param start_date: Starting date
904 @param duration: Duration between start date and end date
905 @param end_date: Ending Datee
906 @param context: A standard dictionary for contextual values
914 if not end_date and not duration:
916 value['duration'] = duration
918 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
919 if allday: # For all day event
921 value['duration'] = duration
922 # change start_date's time to 00:00:00 in the user's timezone
923 user = self.pool.get('res.users').browse(cr, uid, uid)
924 tz = pytz.timezone(user.tz) if user.tz else pytz.utc
925 start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone
926 start = start.replace(hour=0, minute=0, second=0) # change start's time to 00:00:00
927 start = start.astimezone(pytz.utc) # convert start back to utc
928 start_date = start.strftime("%Y-%m-%d %H:%M:%S")
929 value['date'] = start_date
931 if end_date and not duration:
932 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
934 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
935 value['duration'] = round(duration, 2)
937 end = start + timedelta(hours=duration)
938 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
939 elif end_date and duration and not allday:
940 # we have both, keep them synchronized:
941 # set duration based on end_date (arbitrary decision: this avoid
942 # getting dates like 06:31:48 instead of 06:32:00)
943 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
945 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
946 value['duration'] = round(duration, 2)
948 return {'value': value}
950 def unlink_events(self, cr, uid, ids, context=None):
952 This function deletes event which are linked with the event with recurrent_uid
953 (Removes the events which refers to the same UID value)
958 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
959 r_ids = map(lambda x: x[0], cr.fetchall())
960 self.unlink(cr, uid, r_ids, context=context)
963 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
965 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
966 @param self: The object pointer
967 @param cr: the current row, from the database cursor,
968 @param id: List of calendar event's ids.
969 @param context: A standard dictionary for contextual values
970 @return: dictionary of rrule value.
974 if not isinstance(ids, list):
977 for data 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):
979 if data.get('interval', 0) < 0:
980 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.'))
981 if data.get('count', 0) <= 0:
982 raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.'))
983 if data['recurrency']:
984 result[event] = self.compute_rule_string(data)
989 def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
990 data = self._get_empty_rrule_data()
992 data['recurrency'] = True
993 for event in self.browse(cr, uid, ids, context=context):
994 rdate = rule_date or event.date
995 update_data = self._parse_rrule(field_value, dict(data), rdate)
996 data.update(update_data)
997 super(calendar_event, obj).write(cr, uid, ids, data, context=context)
1001 'id': fields.integer('ID', readonly=True),
1002 'sequence': fields.integer('Sequence'),
1003 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1004 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,),
1005 'date_deadline': fields.datetime('End Date', states={'done': [('readonly', True)]}, required=True,),
1006 'create_date': fields.datetime('Created', readonly=True),
1007 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1008 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1009 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1010 ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
1011 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1012 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1013 'Show Time as', states={'done': [('readonly', True)]}),
1014 'base_calendar_url': fields.char('Caldav URL', size=264),
1015 'state': fields.selection([
1016 ('tentative', 'Uncertain'),
1017 ('cancelled', 'Cancelled'),
1018 ('confirmed', 'Confirmed'),
1019 ], 'Status', readonly=True),
1020 'exdate': fields.text('Exception Date/Times', help="This property \
1021 defines the list of date/time exceptions for a recurring calendar component."),
1022 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1023 rule or repeating pattern of time to exclude from the recurring rule."),
1024 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1025 fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
1026 'rrule_type': fields.selection([
1027 ('daily', 'Day(s)'),
1028 ('weekly', 'Week(s)'),
1029 ('monthly', 'Month(s)'),
1030 ('yearly', 'Year(s)')
1031 ], 'Recurrency', states={'done': [('readonly', True)]},
1032 help="Let the event automatically repeat at that interval"),
1033 'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]},
1034 help="Set an alarm at this time, before the event occurs" ),
1035 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1036 'recurrent_uid': fields.integer('Recurrent ID'),
1037 'recurrent_id': fields.datetime('Recurrent ID date'),
1038 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1039 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1040 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with organizer attribute of VEvent.
1041 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1042 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'),
1043 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
1044 'count': fields.integer('Repeat', help="Repeat x times"),
1045 'mo': fields.boolean('Mon'),
1046 'tu': fields.boolean('Tue'),
1047 'we': fields.boolean('Wed'),
1048 'th': fields.boolean('Thu'),
1049 'fr': fields.boolean('Fri'),
1050 'sa': fields.boolean('Sat'),
1051 'su': fields.boolean('Sun'),
1052 'select1': fields.selection([('date', 'Date of month'),
1053 ('day', 'Day of month')], 'Option'),
1054 'day': fields.integer('Date of month'),
1055 'week_list': fields.selection([
1058 ('WE', 'Wednesday'),
1062 ('SU', 'Sunday')], 'Weekday'),
1063 'byday': fields.selection([
1069 ('-1', 'Last')], 'By day'),
1070 'month_list': fields.selection(months.items(), 'Month'),
1071 'end_date': fields.date('Repeat Until'),
1072 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1073 'event_id', 'attendee_id', 'Attendees'),
1074 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1075 'active': fields.boolean('Active', help="If the active field is set to \
1076 true, it will allow you to hide the event alarm information without removing it."),
1077 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1078 'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
1081 def create_attendees(self, cr, uid, ids, context):
1082 att_obj = self.pool.get('calendar.attendee')
1083 user_obj = self.pool.get('res.users')
1084 current_user = user_obj.browse(cr, uid, uid, context=context)
1085 for event in self.browse(cr, uid, ids, context):
1087 for att in event.attendee_ids:
1088 attendees[att.partner_id.id] = True
1091 for partner in event.partner_ids:
1092 if partner.id in attendees:
1094 att_id = self.pool.get('calendar.attendee').create(cr, uid, {
1095 'partner_id': partner.id,
1096 'user_id': partner.user_ids and partner.user_ids[0].id or False,
1097 'ref': self._name+','+str(event.id),
1098 'email': partner.email
1101 mail_to.append(partner.email)
1102 self.write(cr, uid, [event.id], {
1103 'attendee_ids': [(4, att_id)]
1105 new_attendees.append(att_id)
1107 if mail_to and current_user.email:
1108 att_obj._send_mail(cr, uid, new_attendees, mail_to,
1109 email_from = current_user.email)
1112 def default_organizer(self, cr, uid, context=None):
1113 user_pool = self.pool.get('res.users')
1114 user = user_pool.browse(cr, uid, uid, context=context)
1117 res += " <%s>" %(user.email)
1121 'end_type': 'count',
1123 'rrule_type': False,
1124 'state': 'tentative',
1130 'user_id': lambda self, cr, uid, ctx: uid,
1131 'organizer': default_organizer,
1134 def _check_closing_date(self, cr, uid, ids, context=None):
1135 for event in self.browse(cr, uid, ids, context=context):
1136 if event.date_deadline < event.date:
1141 (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']),
1144 def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None):
1145 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1146 This method gives ids of dates that comes between start date and end date of calendar views
1147 @param self: The object pointer
1148 @param cr: the current row, from the database cursor,
1149 @param uid: the current user's ID for security checks,
1150 @param limit: The Number of Results to Return """
1155 for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'exdate', 'exrule', 'date'], context=context):
1156 if not data['rrule']:
1157 result.append(data['id'])
1159 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1161 # TOCHECK: the start date should be replaced by event date; the event date will be changed by that of calendar code
1163 if not data['rrule']:
1166 exdate = data['exdate'] and data['exdate'].split(',') or []
1167 rrule_str = data['rrule']
1169 rrule_until_date = False
1171 for rule in rrule_str.split(';'):
1172 name, value = rule.split('=')
1175 value = parser.parse(value)
1176 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1177 value = value.strftime("%Y%m%d%H%M%S")
1178 new_rule = '%s=%s' % (name, value)
1179 new_rrule_str.append(new_rule)
1180 new_rrule_str = ';'.join(new_rrule_str)
1181 rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
1182 for r_date in rdates:
1185 if arg[0] in ('date', 'date_deadline'):
1187 ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
1189 ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
1191 ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
1193 ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
1195 ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
1198 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1199 result.append(idval)
1201 if isinstance(select, (str, int, long)):
1202 return ids and ids[0] or False
1204 ids = list(set(result))
1207 def compute_rule_string(self, data):
1209 Compute rule string according to value type RECUR of iCalendar from the values given.
1210 @param self: the object pointer
1211 @param data: dictionary of freq and interval value
1212 @return: string containing recurring rule (empty if no rule)
1214 def get_week_string(freq, data):
1215 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1216 if freq == 'weekly':
1217 byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data))
1219 return ';BYDAY=' + ','.join(byday)
1222 def get_month_string(freq, data):
1223 if freq == 'monthly':
1224 if data.get('select1')=='date' and (data.get('day') < 1 or data.get('day') > 31):
1225 raise osv.except_osv(_('Error!'), ("Please select a proper day of the month."))
1227 if data.get('select1')=='day':
1228 return ';BYDAY=' + data.get('byday') + data.get('week_list')
1229 elif data.get('select1')=='date':
1230 return ';BYMONTHDAY=' + str(data.get('day'))
1233 def get_end_date(data):
1234 if data.get('end_date'):
1235 data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z'
1237 return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\
1238 ((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '')
1240 freq = data.get('rrule_type', False)
1243 interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or ''
1244 res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data)
1248 def _get_empty_rrule_data(self):
1251 'recurrency' : False,
1253 'rrule_type' : False,
1270 def _parse_rrule(self, rule, data, date_start):
1271 day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1272 rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1273 r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1275 if r._freq > 0 and r._freq < 4:
1276 data['rrule_type'] = rrule_type[r._freq]
1278 data['count'] = r._count
1279 data['interval'] = r._interval
1280 data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1283 for i in xrange(0,7):
1284 if i in r._byweekday:
1285 data[day_list[i]] = True
1286 data['rrule_type'] = 'weekly'
1287 #repeat monthly by nweekday ((weekday, weeknumber), )
1289 data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1290 data['byday'] = r._bynweekday[0][1]
1291 data['select1'] = 'day'
1292 data['rrule_type'] = 'monthly'
1295 data['day'] = r._bymonthday[0]
1296 data['select1'] = 'date'
1297 data['rrule_type'] = 'monthly'
1299 #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1301 data['interval'] = data['interval'] * 12
1303 #FIXEME handle forever case
1305 #in case of repeat for ever that we do not support right now
1306 if not (data.get('count') or data.get('end_date')):
1308 if data.get('count'):
1309 data['end_type'] = 'count'
1311 data['end_type'] = 'end_date'
1314 def remove_virtual_id(self, ids):
1315 if isinstance(ids, (str, int, long)):
1316 return base_calendar_id2real_id(ids)
1318 if isinstance(ids, (list, tuple)):
1321 res.append(base_calendar_id2real_id(id))
1324 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1325 context = context or {}
1326 args_without_date = []
1331 new_id = self.remove_virtual_id(arg[2])
1332 new_arg = (arg[0], arg[1], new_id)
1333 args_without_date.append(new_arg)
1334 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1335 args_without_date.append(arg)
1337 if context.get('virtual_id', True):
1338 args_without_date.append('|')
1339 args_without_date.append(arg)
1340 if context.get('virtual_id', True):
1341 args_without_date.append(('recurrency','=',1))
1342 filter_date.append(arg)
1344 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1345 0, 0, order, context, count=False)
1346 if context.get('virtual_id', True):
1347 res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
1352 return res[offset:offset+limit]
1356 def _get_data(self, cr, uid, id, context=None):
1357 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1360 def need_to_update(self, event_id, vals):
1361 split_id = str(event_id).split("-")
1362 if len(split_id) < 2:
1365 date_start = vals.get('date', '')
1367 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1368 return date_start == split_id[1]
1372 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1373 context = context or {}
1374 if isinstance(ids, (str, int, long)):
1378 # Special write of complex IDS
1379 for event_id in ids[:]:
1380 if len(str(event_id).split('-')) == 1:
1382 ids.remove(event_id)
1383 real_event_id = base_calendar_id2real_id(event_id)
1384 if not vals.get('recurrency', True):
1385 ids.append(real_event_id)
1388 #if edit one instance of a reccurrent id
1389 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1390 'rrule', 'duration', 'exdate'])
1391 if data.get('rrule'):
1394 recurrent_uid=real_event_id,
1395 recurrent_id=data.get('date'),
1401 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1403 date_new = event_id.split('-')[1]
1404 date_new = time.strftime("%Y%m%dT%H%M%S", \
1405 time.strptime(date_new, "%Y%m%d%H%M%S"))
1406 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1407 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1409 context.update({'active_id': new_id, 'active_ids': [new_id]})
1412 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1413 vals['vtimezone'] = vals['vtimezone'][40:]
1415 res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
1416 if vals.get('partner_ids', False):
1417 self.create_attendees(cr, uid, ids, context)
1419 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1420 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1421 alarm_obj = self.pool.get('res.alarm')
1422 alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context)
1423 return res or True and False
1425 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1429 if 'date' in groupby:
1430 raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.'))
1431 virtual_id = context.get('virtual_id', True)
1432 context.update({'virtual_id': False})
1433 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1435 #remove the count, since the value is not consistent with the result of the search when expand the group
1436 for groupname in groupby:
1437 if re.get(groupname + "_count"):
1438 del re[groupname + "_count"]
1439 re.get('__context', {}).update({'virtual_id' : virtual_id})
1442 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1445 fields2 = fields and fields[:] or None
1447 EXTRAFIELDS = ('class','user_id','duration')
1448 for f in EXTRAFIELDS:
1449 if fields and (f not in fields):
1452 # FIXME This whole id mangling has to go!
1453 if isinstance(ids, (str, int, long)):
1458 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1461 real_data = super(calendar_event, self).read(cr, uid,
1462 [real_id for base_calendar_id, real_id in select],
1463 fields=fields2, context=context, load=load)
1464 real_data = dict(zip([x['id'] for x in real_data], real_data))
1466 for base_calendar_id, real_id in select:
1467 res = real_data[real_id].copy()
1468 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1469 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1471 res['date_deadline'] = ls[2]
1472 res['id'] = base_calendar_id
1478 user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id']
1481 if r['class']=='private':
1483 if f not in ('id','date','date_deadline','duration','user_id','state'):
1484 if isinstance(r[f], list):
1492 for k in EXTRAFIELDS:
1493 if (k in r) and ((not fields) or (k not in fields)):
1495 if isinstance(ids, (str, int, long)):
1496 return result and result[0] or False
1499 def copy(self, cr, uid, id, default=None, context=None):
1503 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1504 alarm_obj = self.pool.get('res.alarm')
1505 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1508 def unlink(self, cr, uid, ids, context=None):
1509 if not isinstance(ids, list):
1512 attendee_obj=self.pool.get('calendar.attendee')
1513 for event_id in ids[:]:
1514 if len(str(event_id).split('-')) == 1:
1517 real_event_id = base_calendar_id2real_id(event_id)
1518 data = self.read(cr, uid, real_event_id, ['exdate'], context=context)
1519 date_new = event_id.split('-')[1]
1520 date_new = time.strftime("%Y%m%dT%H%M%S", \
1521 time.strptime(date_new, "%Y%m%d%H%M%S"))
1522 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1523 self.write(cr, uid, [real_event_id], {'exdate': exdate})
1524 ids.remove(event_id)
1525 for event in self.browse(cr, uid, ids, context=context):
1526 if event.attendee_ids:
1527 attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context)
1529 res = super(calendar_event, self).unlink(cr, uid, ids, context=context)
1530 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name)
1531 self.unlink_events(cr, uid, ids, context=context)
1534 def create(self, cr, uid, vals, context=None):
1538 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1539 vals['vtimezone'] = vals['vtimezone'][40:]
1541 res = super(calendar_event, self).create(cr, uid, vals, context)
1542 alarm_obj = self.pool.get('res.alarm')
1543 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1544 self.create_attendees(cr, uid, [res], context)
1547 def do_tentative(self, cr, uid, ids, context=None, *args):
1548 """ Makes event invitation as Tentative
1549 @param self: The object pointer
1550 @param cr: the current row, from the database cursor,
1551 @param uid: the current user's ID for security checks,
1552 @param ids: List of Event IDs
1553 @param *args: Get Tupple value
1554 @param context: A standard dictionary for contextual values
1556 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1558 def do_cancel(self, cr, uid, ids, context=None, *args):
1559 """ Makes event invitation as Tentative
1560 @param self: The object pointer
1561 @param cr: the current row, from the database cursor,
1562 @param uid: the current user's ID for security checks,
1563 @param ids: List of Event IDs
1564 @param *args: Get Tupple value
1565 @param context: A standard dictionary for contextual values
1567 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1569 def do_confirm(self, cr, uid, ids, context=None, *args):
1570 """ Makes event invitation as Tentative
1571 @param self: The object pointer
1572 @param cr: the current row, from the database cursor,
1573 @param uid: the current user's ID for security checks,
1574 @param ids: List of Event IDs
1575 @param *args: Get Tupple value
1576 @param context: A standard dictionary for contextual values
1578 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1582 class calendar_todo(osv.osv):
1583 """ Calendar Task """
1585 _name = "calendar.todo"
1586 _inherit = "calendar.event"
1587 _description = "Calendar Task"
1589 def _get_date(self, cr, uid, ids, name, arg, context=None):
1592 @param self: The object pointer
1593 @param cr: the current row, from the database cursor,
1594 @param uid: the current user's ID for security checks,
1595 @param ids: List of calendar todo's IDs.
1596 @param args: list of tuples of form [(‘name_of_the_field', ‘operator', value), ...].
1597 @param context: A standard dictionary for contextual values
1601 for event in self.browse(cr, uid, ids, context=context):
1602 res[event.id] = event.date_start
1605 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1608 @param self: The object pointer
1609 @param cr: the current row, from the database cursor,
1610 @param uid: the current user's ID for security checks,
1611 @param id: calendar's ID.
1612 @param value: Get Value
1613 @param args: list of tuples of form [('name_of_the_field', 'operator', value), ...].
1614 @param context: A standard dictionary for contextual values
1617 assert name == 'date'
1618 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1621 'date': fields.function(_get_date, fnct_inv=_set_date, \
1622 string='Duration', store=True, type='datetime'),
1623 'duration': fields.integer('Duration'),
1632 class ir_values(osv.osv):
1633 _inherit = 'ir.values'
1635 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1636 isobject=False, meta=False, preserve_user=False, company=False):
1639 @param self: The object pointer
1640 @param cr: the current row, from the database cursor,
1641 @param uid: the current user's ID for security checks,
1642 @param model: Get The Model
1647 if type(data) in (list, tuple):
1648 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1650 new_model.append(data)
1651 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1652 value, replace, isobject, meta, preserve_user, company)
1654 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1655 res_id_req=False, without_user=True, key2_req=True):
1658 @param self: The object pointer
1659 @param cr: the current row, from the database cursor,
1660 @param uid: the current user's ID for security checks,
1661 @param model: Get The Model
1667 if type(data) in (list, tuple):
1668 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1670 new_model.append(data)
1671 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1672 meta, context, res_id_req, without_user, key2_req)
1676 class ir_model(osv.osv):
1678 _inherit = 'ir.model'
1680 def read(self, cr, uid, ids, fields=None, context=None,
1681 load='_classic_read'):
1683 Overrides orm read method.
1684 @param self: The object pointer
1685 @param cr: the current row, from the database cursor,
1686 @param uid: the current user's ID for security checks,
1687 @param ids: List of IR Model's IDs.
1688 @param context: A standard dictionary for contextual values
1690 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1693 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1694 context=context, load=load)
1697 val['id'] = base_calendar_id2real_id(val['id'])
1698 return isinstance(ids, (str, int, long)) and data[0] or data
1702 class virtual_report_spool(web_services.report_spool):
1704 def exp_report(self, db, uid, object, ids, data=None, context=None):
1707 @param self: The object pointer
1708 @param db: get the current database,
1709 @param uid: the current user's ID for security checks,
1710 @param context: A standard dictionary for contextual values
1713 if object == 'printscreen.list':
1714 return super(virtual_report_spool, self).exp_report(db, uid, \
1715 object, ids, data, context)
1718 new_ids.append(base_calendar_id2real_id(id))
1719 if data.get('id', False):
1720 data['id'] = base_calendar_id2real_id(data['id'])
1721 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, data, context)
1723 virtual_report_spool()
1725 class res_users(osv.osv):
1726 _inherit = 'res.users'
1728 def _get_user_avail(self, cr, uid, ids, context=None):
1730 Get User Availability
1731 @param self: The object pointer
1732 @param cr: the current row, from the database cursor,
1733 @param uid: the current user's ID for security checks,
1734 @param ids: List of res user's IDs.
1735 @param context: A standard dictionary for contextual values
1738 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1740 attendee_obj = self.pool.get('calendar.attendee')
1741 attendee_ids = attendee_obj.search(cr, uid, [
1742 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1743 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1746 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1747 user_id = attendee_data['user_id']
1749 res.update({user_id:status})
1751 #TOCHECK: Delegated Event
1753 if user_id not in res:
1754 res[user_id] = 'free'
1758 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1760 Get User Availability Function
1761 @param self: The object pointer
1762 @param cr: the current row, from the database cursor,
1763 @param uid: the current user's ID for security checks,
1764 @param ids: List of res user's IDs.
1765 @param context: A standard dictionary for contextual values
1768 return self._get_user_avail(cr, uid, ids, context=context)
1771 'availability': fields.function(_get_user_avail_fun, type='selection', \
1772 selection=[('free', 'Free'), ('busy', 'Busy')], \
1773 string='Free/Busy'),
1778 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: