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.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, ['lang'], 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!'),_("First you have to specify the date of the invitation."))
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.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,
518 content_subtype='html',
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.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.email]
895 for att in alarm.attendee_ids:
896 mail_to.append(att.user_id.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 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
945 if allday: # For all day event
947 value['duration'] = duration
948 # change start_date's time to 00:00:00 in the user's timezone
949 user = self.pool.get('res.users').browse(cr, uid, uid)
950 tz = pytz.timezone(user.tz) if user.tz else pytz.utc
951 start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone
952 start = start.replace(hour=0, minute=0, second=0) # change start's time to 00:00:00
953 start = start.astimezone(pytz.utc) # convert start back to utc
954 start_date = start.strftime("%Y-%m-%d %H:%M:%S")
955 value['date'] = start_date
957 if end_date and not duration:
958 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
960 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
961 value['duration'] = round(duration, 2)
963 end = start + timedelta(hours=duration)
964 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
965 elif end_date and duration and not allday:
966 # we have both, keep them synchronized:
967 # set duration based on end_date (arbitrary decision: this avoid
968 # getting dates like 06:31:48 instead of 06:32:00)
969 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
971 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
972 value['duration'] = round(duration, 2)
974 return {'value': value}
976 def unlink_events(self, cr, uid, ids, context=None):
978 This function deletes event which are linked with the event with recurrent_uid
979 (Removes the events which refers to the same UID value)
984 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
985 r_ids = map(lambda x: x[0], cr.fetchall())
986 self.unlink(cr, uid, r_ids, context=context)
989 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
991 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
992 @param self: The object pointer
993 @param cr: the current row, from the database cursor,
994 @param id: List of calendar event's ids.
995 @param context: A standard dictionary for contextual values
996 @return: dictionary of rrule value.
1000 if not isinstance(ids, list):
1003 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):
1005 if datas.get('interval', 0) < 0:
1006 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.'))
1007 if datas.get('count', 0) < 0:
1008 raise osv.except_osv(_('Warning!'), _('Count cannot be negative.'))
1009 if datas['recurrency']:
1010 result[event] = self.compute_rule_string(datas)
1015 def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
1016 data = self._get_empty_rrule_data()
1018 data['recurrency'] = True
1019 for event in self.browse(cr, uid, ids, context=context):
1020 rdate = rule_date or event.date
1021 update_data = self._parse_rrule(field_value, dict(data), rdate)
1022 data.update(update_data)
1023 super(calendar_event, obj).write(cr, uid, ids, data, context=context)
1028 'id': fields.integer('ID', readonly=True),
1029 'sequence': fields.integer('Sequence'),
1030 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1031 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,),
1032 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}, required=True,),
1033 'create_date': fields.datetime('Created', readonly=True),
1034 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1035 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1036 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1037 ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
1038 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1039 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1040 'Show Time as', states={'done': [('readonly', True)]}),
1041 'base_calendar_url': fields.char('Caldav URL', size=264),
1042 'state': fields.selection([('tentative', 'Tentative'),
1043 ('cancelled', 'Cancelled'),
1044 ('confirmed', 'Confirmed'),
1045 ], 'Status', readonly=True),
1046 'exdate': fields.text('Exception Date/Times', help="This property \
1047 defines the list of date/time exceptions for a recurring calendar component."),
1048 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1049 rule or repeating pattern of time to exclude from the recurring rule."),
1050 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1051 fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
1052 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1053 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1054 ('yearly', 'Yearly'),],
1055 'Recurrency', states={'done': [('readonly', True)]},
1056 help="Let the event automatically repeat at that interval"),
1057 'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]},
1058 help="Set an alarm at this time, before the event occurs" ),
1059 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1060 'recurrent_uid': fields.integer('Recurrent ID'),
1061 'recurrent_id': fields.datetime('Recurrent ID date'),
1062 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1063 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1064 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1065 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1066 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'),
1067 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
1068 'count': fields.integer('Repeat', help="Repeat x times"),
1069 'mo': fields.boolean('Mon'),
1070 'tu': fields.boolean('Tue'),
1071 'we': fields.boolean('Wed'),
1072 'th': fields.boolean('Thu'),
1073 'fr': fields.boolean('Fri'),
1074 'sa': fields.boolean('Sat'),
1075 'su': fields.boolean('Sun'),
1076 'select1': fields.selection([('date', 'Date of month'),
1077 ('day', 'Day of month')], 'Option'),
1078 'day': fields.integer('Date of month'),
1079 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1080 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1081 ('FR', 'Friday'), ('SA', 'Saturday'), \
1082 ('SU', 'Sunday')], 'Weekday'),
1083 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1084 ('3', 'Third'), ('4', 'Fourth'), \
1085 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1086 'month_list': fields.selection(months.items(), 'Month'),
1087 'end_date': fields.date('Repeat Until'),
1088 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1089 'event_id', 'attendee_id', 'Attendees'),
1090 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1091 'active': fields.boolean('Active', help="If the active field is set to \
1092 true, it will allow you to hide the event alarm information without removing it."),
1093 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1096 def default_organizer(self, cr, uid, context=None):
1097 user_pool = self.pool.get('res.users')
1098 user = user_pool.browse(cr, uid, uid, context=context)
1101 res += " <%s>" %(user.email)
1105 'end_type' : 'count',
1107 'rrule_type' : 'none',
1108 'state': 'tentative',
1114 'user_id': lambda self, cr, uid, ctx: uid,
1115 'organizer': default_organizer,
1118 def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None):
1119 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1120 This method gives ids of dates that comes between start date and end date of calendar views
1121 @param self: The object pointer
1122 @param cr: the current row, from the database cursor,
1123 @param uid: the current user’s ID for security checks,
1124 @param limit: The Number of Results to Return """
1129 for data in super(calendar_event, self).read(cr, uid, select, context=context):
1130 if not data['rrule']:
1131 result.append(data['id'])
1133 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1134 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1136 if not data['rrule']:
1139 exdate = data['exdate'] and data['exdate'].split(',') or []
1140 rrule_str = data['rrule']
1142 rrule_until_date = False
1144 for rule in rrule_str.split(';'):
1145 name, value = rule.split('=')
1148 value = parser.parse(value)
1149 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1150 value = value.strftime("%Y%m%d%H%M%S")
1151 new_rule = '%s=%s' % (name, value)
1152 new_rrule_str.append(new_rule)
1153 new_rrule_str = ';'.join(new_rrule_str)
1154 rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
1155 for r_date in rdates:
1158 if arg[0] in ('date', 'date_deadline'):
1160 ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
1162 ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
1164 ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
1166 ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
1168 ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
1171 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1172 result.append(idval)
1174 if isinstance(select, (str, int, long)):
1175 return ids and ids[0] or False
1177 ids = list(set(result))
1180 def compute_rule_string(self, datas):
1182 Compute rule string according to value type RECUR of iCalendar from the values given.
1183 @param self: the object pointer
1184 @param datas: dictionary of freq and interval value.
1186 def get_week_string(freq, datas):
1187 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1188 if freq == 'weekly':
1189 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1191 return ';BYDAY=' + ','.join(byday)
1194 def get_month_string(freq, datas):
1195 if freq == 'monthly':
1196 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1197 raise osv.except_osv(_('Error!'), ("Please select a proper day of the month."))
1199 if datas.get('select1')=='day':
1200 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1201 elif datas.get('select1')=='date':
1202 return ';BYMONTHDAY=' + str(datas.get('day'))
1205 def get_end_date(datas):
1206 if datas.get('end_date'):
1207 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1209 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1210 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1212 freq=datas.get('rrule_type')
1216 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1218 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1220 def _get_empty_rrule_data(self):
1223 'recurrency' : False,
1225 'rrule_type' : False,
1242 #def _write_rrule(self, cr, uid, ids, field_value, rule_date=False, context=None):
1243 # data = self._get_empty_rrule_data()
1246 # data['recurrency'] = True
1247 # for event in self.browse(cr, uid, ids, context=context):
1248 # rdate = rule_date or event.date
1249 # update_data = self._parse_rrule(field_value, dict(data), rdate)
1250 # data.update(update_data)
1252 # self.write(cr, uid, event.id, data, context=context)
1255 def _parse_rrule(self, rule, data, date_start):
1256 day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1257 rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1258 r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1260 if r._freq > 0 and r._freq < 4:
1261 data['rrule_type'] = rrule_type[r._freq]
1263 data['count'] = r._count
1264 data['interval'] = r._interval
1265 data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1268 for i in xrange(0,7):
1269 if i in r._byweekday:
1270 data[day_list[i]] = True
1271 data['rrule_type'] = 'weekly'
1272 #repeat monthly bynweekday ((weekday, weeknumber), )
1274 data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1275 data['byday'] = r._bynweekday[0][1]
1276 data['select1'] = 'day'
1277 data['rrule_type'] = 'monthly'
1280 data['day'] = r._bymonthday[0]
1281 data['select1'] = 'date'
1282 data['rrule_type'] = 'monthly'
1284 #yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1286 data['interval'] = data['interval'] * 12
1288 #FIXEME handle forever case
1290 #in case of repeat for ever that we do not support right now
1291 if not (data.get('count') or data.get('end_date')):
1293 if data.get('count'):
1294 data['end_type'] = 'count'
1296 data['end_type'] = 'end_date'
1299 def remove_virtual_id(self, ids):
1300 if isinstance(ids, (str, int, long)):
1301 return base_calendar_id2real_id(ids)
1303 if isinstance(ids, (list, tuple)):
1306 res.append(base_calendar_id2real_id(id))
1309 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1310 context = context or {}
1311 args_without_date = []
1316 new_id = self.remove_virtual_id(arg[2])
1317 new_arg = (arg[0], arg[1], new_id)
1318 args_without_date.append(new_arg)
1319 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1320 args_without_date.append(arg)
1322 if context.get('virtual_id', True):
1323 args_without_date.append('|')
1324 args_without_date.append(arg)
1325 if context.get('virtual_id', True):
1326 args_without_date.append(('recurrency','=',1))
1327 filter_date.append(arg)
1329 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1330 0, 0, order, context, count=False)
1331 if context.get('virtual_id', True):
1332 res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
1337 return res[offset:offset+limit]
1341 def _get_data(self, cr, uid, id, context=None):
1342 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1345 def need_to_update(self, event_id, vals):
1346 split_id = str(event_id).split("-")
1347 if len(split_id) < 2:
1350 date_start = vals.get('date', '')
1352 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1353 return date_start == split_id[1]
1358 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1359 context = context or {}
1360 if isinstance(ids, (str, int, long)):
1364 # Special write of complex IDS
1365 for event_id in ids[:]:
1366 if len(str(event_id).split('-')) == 1:
1368 ids.remove(event_id)
1369 real_event_id = base_calendar_id2real_id(event_id)
1370 if not vals.get('recurrency', True):
1371 ids.append(real_event_id)
1374 #if edit one instance of a reccurrent id
1375 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1376 'rrule', 'duration', 'exdate'])
1377 if data.get('rrule'):
1380 'recurrent_uid': real_event_id,
1381 'recurrent_id': data.get('date'),
1382 'rrule_type': 'none',
1384 'recurrency' : False,
1387 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1389 date_new = event_id.split('-')[1]
1390 date_new = time.strftime("%Y%m%dT%H%M%S", \
1391 time.strptime(date_new, "%Y%m%d%H%M%S"))
1392 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1393 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1395 context.update({'active_id': new_id, 'active_ids': [new_id]})
1398 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1399 vals['vtimezone'] = vals['vtimezone'][40:]
1401 res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
1403 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1404 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1405 alarm_obj = self.pool.get('res.alarm')
1406 alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context)
1407 return res or True and False
1409 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1413 if 'date' in groupby:
1414 raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.'))
1415 virtual_id = context.get('virtual_id', True)
1416 context.update({'virtual_id': False})
1417 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1419 #remove the count, since the value is not consistent with the result of the search when expand the group
1420 for groupname in groupby:
1421 if re.get(groupname + "_count"):
1422 del re[groupname + "_count"]
1423 re.get('__context', {}).update({'virtual_id' : virtual_id})
1426 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1427 # FIXME This whole id mangling has to go!
1430 fields2 = fields and fields[:] or None
1432 EXTRAFIELDS = ('class','user_id','duration')
1433 for f in EXTRAFIELDS:
1434 if fields and (f not in fields):
1437 if isinstance(ids, (str, int, long)):
1441 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1444 real_data = super(calendar_event, self).read(cr, uid,
1445 [real_id for base_calendar_id, real_id in select],
1446 fields=fields2, context=context, load=load)
1447 real_data = dict(zip([x['id'] for x in real_data], real_data))
1449 for base_calendar_id, real_id in select:
1450 res = real_data[real_id].copy()
1451 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1452 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1454 res['date_deadline'] = ls[2]
1455 res['id'] = base_calendar_id
1461 user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id']
1464 if r['class']=='private':
1466 if f not in ('id','date','date_deadline','duration','user_id','state'):
1472 for k in EXTRAFIELDS:
1473 if (k in r) and ((not fields) or (k not in fields)):
1475 if isinstance(ids, (str, int, long)):
1476 return result and result[0] or False
1479 def copy(self, cr, uid, id, default=None, context=None):
1483 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1484 alarm_obj = self.pool.get('res.alarm')
1485 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1488 def unlink(self, cr, uid, ids, context=None):
1489 if not isinstance(ids, list):
1492 attendee_obj=self.pool.get('calendar.attendee')
1493 for event_id in ids[:]:
1494 if len(str(event_id).split('-')) == 1:
1497 real_event_id = base_calendar_id2real_id(event_id)
1498 data = self.read(cr, uid, real_event_id, ['exdate'], context=context)
1499 date_new = event_id.split('-')[1]
1500 date_new = time.strftime("%Y%m%dT%H%M%S", \
1501 time.strptime(date_new, "%Y%m%d%H%M%S"))
1502 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1503 self.write(cr, uid, [real_event_id], {'exdate': exdate})
1504 ids.remove(event_id)
1505 for event in self.browse(cr, uid, ids, context=context):
1506 if event.attendee_ids:
1507 attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context)
1509 res = super(calendar_event, self).unlink(cr, uid, ids, context=context)
1510 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name)
1511 self.unlink_events(cr, uid, ids, context=context)
1515 def create(self, cr, uid, vals, context=None):
1519 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1520 vals['vtimezone'] = vals['vtimezone'][40:]
1522 #updated_vals = self.onchange_dates(cr, uid, [],
1523 # vals.get('date', False),
1524 # vals.get('duration', False),
1525 # vals.get('date_deadline', False),
1526 # vals.get('allday', False),
1528 #vals.update(updated_vals.get('value', {}))
1530 res = super(calendar_event, self).create(cr, uid, vals, context)
1531 alarm_obj = self.pool.get('res.alarm')
1532 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1535 def do_tentative(self, cr, uid, ids, context=None, *args):
1536 """ Makes event invitation as Tentative
1537 @param self: The object pointer
1538 @param cr: the current row, from the database cursor,
1539 @param uid: the current user’s ID for security checks,
1540 @param ids: List of Event IDs
1541 @param *args: Get Tupple value
1542 @param context: A standard dictionary for contextual values
1544 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1546 def do_cancel(self, cr, uid, ids, context=None, *args):
1547 """ Makes event invitation as Tentative
1548 @param self: The object pointer
1549 @param cr: the current row, from the database cursor,
1550 @param uid: the current user’s ID for security checks,
1551 @param ids: List of Event IDs
1552 @param *args: Get Tupple value
1553 @param context: A standard dictionary for contextual values
1555 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1557 def do_confirm(self, cr, uid, ids, context=None, *args):
1558 """ Makes event invitation as Tentative
1559 @param self: The object pointer
1560 @param cr: the current row, from the database cursor,
1561 @param uid: the current user’s ID for security checks,
1562 @param ids: List of Event IDs
1563 @param *args: Get Tupple value
1564 @param context: A standard dictionary for contextual values
1566 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1570 class calendar_todo(osv.osv):
1571 """ Calendar Task """
1573 _name = "calendar.todo"
1574 _inherit = "calendar.event"
1575 _description = "Calendar Task"
1577 def _get_date(self, cr, uid, ids, name, arg, context=None):
1580 @param self: The object pointer
1581 @param cr: the current row, from the database cursor,
1582 @param uid: the current user’s ID for security checks,
1583 @param ids: List of calendar todo's IDs.
1584 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1585 @param context: A standard dictionary for contextual values
1589 for event in self.browse(cr, uid, ids, context=context):
1590 res[event.id] = event.date_start
1593 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1596 @param self: The object pointer
1597 @param cr: the current row, from the database cursor,
1598 @param uid: the current user’s ID for security checks,
1599 @param id: calendar's ID.
1600 @param value: Get Value
1601 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1602 @param context: A standard dictionary for contextual values
1605 assert name == 'date'
1606 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1609 'date': fields.function(_get_date, fnct_inv=_set_date, \
1610 string='Duration', store=True, type='datetime'),
1611 'duration': fields.integer('Duration'),
1619 class ir_attachment(osv.osv):
1620 _name = 'ir.attachment'
1621 _inherit = 'ir.attachment'
1623 def search_count(self, cr, user, args, context=None):
1625 for domain_item in args:
1626 if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
1627 new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
1629 new_args.append(domain_item)
1630 return super(ir_attachment, self).search_count(cr, user, new_args, context)
1632 def create(self, cr, uid, vals, context=None):
1634 id = context.get('default_res_id', False)
1635 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1636 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1638 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1639 context=None, count=False):
1641 for domain_item in args:
1642 if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
1643 new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
1645 new_args.append(domain_item)
1646 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1647 limit=limit, order=order, context=context, count=False)
1650 class ir_values(osv.osv):
1651 _inherit = 'ir.values'
1653 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1654 isobject=False, meta=False, preserve_user=False, company=False):
1657 @param self: The object pointer
1658 @param cr: the current row, from the database cursor,
1659 @param uid: the current user’s ID for security checks,
1660 @param model: Get The Model
1665 if type(data) in (list, tuple):
1666 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1668 new_model.append(data)
1669 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1670 value, replace, isobject, meta, preserve_user, company)
1672 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1673 res_id_req=False, without_user=True, key2_req=True):
1676 @param self: The object pointer
1677 @param cr: the current row, from the database cursor,
1678 @param uid: the current user’s ID for security checks,
1679 @param model: Get The Model
1685 if type(data) in (list, tuple):
1686 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1688 new_model.append(data)
1689 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1690 meta, context, res_id_req, without_user, key2_req)
1694 class ir_model(osv.osv):
1696 _inherit = 'ir.model'
1698 def read(self, cr, uid, ids, fields=None, context=None,
1699 load='_classic_read'):
1701 Overrides orm read method.
1702 @param self: The object pointer
1703 @param cr: the current row, from the database cursor,
1704 @param uid: the current user’s ID for security checks,
1705 @param ids: List of IR Model’s IDs.
1706 @param context: A standard dictionary for contextual values
1708 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1711 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1712 context=context, load=load)
1715 val['id'] = base_calendar_id2real_id(val['id'])
1716 return isinstance(ids, (str, int, long)) and data[0] or data
1720 class virtual_report_spool(web_services.report_spool):
1722 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1725 @param self: The object pointer
1726 @param db: get the current database,
1727 @param uid: the current user’s ID for security checks,
1728 @param context: A standard dictionary for contextual values
1731 if object == 'printscreen.list':
1732 return super(virtual_report_spool, self).exp_report(db, uid, \
1733 object, ids, datas, context)
1736 new_ids.append(base_calendar_id2real_id(id))
1737 if datas.get('id', False):
1738 datas['id'] = base_calendar_id2real_id(datas['id'])
1739 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1741 virtual_report_spool()
1743 class res_users(osv.osv):
1744 _inherit = 'res.users'
1746 def _get_user_avail(self, cr, uid, ids, context=None):
1748 Get User Availability
1749 @param self: The object pointer
1750 @param cr: the current row, from the database cursor,
1751 @param uid: the current user’s ID for security checks,
1752 @param ids: List of res user’s IDs.
1753 @param context: A standard dictionary for contextual values
1756 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1758 attendee_obj = self.pool.get('calendar.attendee')
1759 attendee_ids = attendee_obj.search(cr, uid, [
1760 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1761 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1764 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1765 user_id = attendee_data['user_id']
1767 res.update({user_id:status})
1769 #TOCHECK: Delegated Event
1771 if user_id not in res:
1772 res[user_id] = 'free'
1776 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1778 Get User Availability Function
1779 @param self: The object pointer
1780 @param cr: the current row, from the database cursor,
1781 @param uid: the current user’s ID for security checks,
1782 @param ids: List of res user’s IDs.
1783 @param context: A standard dictionary for contextual values
1786 return self._get_user_avail(cr, uid, ids, context=context)
1789 'availability': fields.function(_get_user_avail_fun, type='selection', \
1790 selection=[('free', 'Free'), ('busy', 'Busy')], \
1791 string='Free/Busy'),
1797 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: