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 email_from: Email address for user sending the mail
477 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
478 for att in self.browse(cr, uid, ids, context=context):
479 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
480 sign = '<br>'.join(sign and sign.split('\n') or [])
485 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
487 for att2 in self.browse(cr, uid, other_invitation_ids):
488 att_infos.append(((att2.user_id and att2.user_id.name) or \
489 (att2.partner_id and att2.partner_id.name) or \
490 att2.email) + ' - Status: ' + att2.state.title())
491 body_vals = {'name': res_obj.name,
492 'start_date': res_obj.date,
493 'end_date': res_obj.date_deadline or False,
494 'description': res_obj.description or '-',
495 'location': res_obj.location or '-',
496 'attendees': '<br>'.join(att_infos),
497 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
501 body = html_invitation % body_vals
502 if mail_to and email_from:
503 ics_file = self.get_ics_file(cr, uid, res_obj, context=context)
504 vals = {'email_from': email_from,
511 vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics',
512 'datas_fname': 'invitation.ics',
513 'datas': str(ics_file).encode('base64')})]
514 self.pool.get('mail.mail').create(cr, uid, vals, context=context)
517 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
519 Make entry on email and availbility on change of user_id field.
520 @param cr: the current row, from the database cursor,
521 @param uid: the current user’s ID for security checks,
522 @param ids: List of calendar attendee’s IDs.
523 @param user_id: Changed value of User id
524 @return: dictionary of value. which put value in email and availability fields.
528 return {'value': {'email': ''}}
529 usr_obj = self.pool.get('res.users')
530 user = usr_obj.browse(cr, uid, user_id, *args)
531 return {'value': {'email': user.email, 'availability':user.availability}}
533 def do_tentative(self, cr, uid, ids, context=None, *args):
534 """ Makes event invitation as Tentative
535 @param self: The object pointer
536 @param cr: the current row, from the database cursor,
537 @param uid: the current user’s ID for security checks,
538 @param ids: List of calendar attendee’s IDs
539 @param *args: Get Tupple value
540 @param context: A standard dictionary for contextual values
542 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
544 def do_accept(self, cr, uid, ids, context=None, *args):
546 Update state of invitation as Accepted and
547 if the invited user is other then event user it will make a copy of this event for invited user
548 @param cr: the current row, from the database cursor,
549 @param uid: the current user’s ID for security checks,
550 @param ids: List of calendar attendee’s IDs.
551 @param context: A standard dictionary for contextual values
557 for vals in self.browse(cr, uid, ids, context=context):
558 if vals.ref and vals.ref.user_id:
559 mod_obj = self.pool.get(vals.ref._name)
560 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
561 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
562 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
566 def do_decline(self, cr, uid, ids, context=None, *args):
567 """ Marks event invitation as Declined
568 @param self: The object pointer
569 @param cr: the current row, from the database cursor,
570 @param uid: the current user’s ID for security checks,
571 @param ids: List of calendar attendee’s IDs
572 @param *args: Get Tupple value
573 @param context: A standard dictionary for contextual values """
576 return self.write(cr, uid, ids, {'state': 'declined'}, context)
578 def create(self, cr, uid, vals, context=None):
579 """ Overrides orm create method.
580 @param self: The object pointer
581 @param cr: the current row, from the database cursor,
582 @param uid: the current user’s ID for security checks,
583 @param vals: Get Values
584 @param context: A standard dictionary for contextual values """
588 if not vals.get("email") and vals.get("cn"):
589 cnval = vals.get("cn").split(':')
590 email = filter(lambda x:x.__contains__('@'), cnval)
591 vals['email'] = email and email[0] or ''
592 vals['cn'] = vals.get("cn")
593 res = super(calendar_attendee, self).create(cr, uid, vals, context)
597 class res_alarm(osv.osv):
598 """Resource Alarm """
600 _description = 'Basic Alarm Information'
603 'name':fields.char('Name', size=256, required=True),
604 'trigger_occurs': fields.selection([('before', 'Before'), \
605 ('after', 'After')], \
606 'Triggers', required=True),
607 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
608 ('hours', 'Hours'), \
609 ('days', 'Days')], 'Interval', \
611 'trigger_duration': fields.integer('Duration', required=True),
612 'trigger_related': fields.selection([('start', 'The event starts'), \
613 ('end', 'The event ends')], \
614 'Related to', required=True),
615 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
616 are both optional, but if one occurs, so MUST the other"""),
617 'repeat': fields.integer('Repeat'),
618 'active': fields.boolean('Active', help="If the active field is set to \
619 true, it will allow you to hide the event alarm information without removing it.")
622 'trigger_interval': 'minutes',
623 'trigger_duration': 5,
624 'trigger_occurs': 'before',
625 'trigger_related': 'start',
629 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
631 Create Alarm for event.
632 @param cr: the current row, from the database cursor,
633 @param uid: the current user’s ID for security checks,
634 @param ids: List of res alarm’s IDs.
635 @param model: Model name.
636 @param date: Event date
637 @param context: A standard dictionary for contextual values
642 alarm_obj = self.pool.get('calendar.alarm')
643 res_alarm_obj = self.pool.get('res.alarm')
644 ir_obj = self.pool.get('ir.model')
645 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
647 model_obj = self.pool.get(model)
648 for data in model_obj.browse(cr, uid, ids, context=context):
650 basic_alarm = data.alarm_id
651 cal_alarm = data.base_calendar_alarm_id
652 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
654 # Find for existing res.alarm
655 duration = cal_alarm.trigger_duration
656 interval = cal_alarm.trigger_interval
657 occurs = cal_alarm.trigger_occurs
658 related = cal_alarm.trigger_related
659 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
660 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
663 'trigger_duration': duration,
664 'trigger_interval': interval,
665 'trigger_occurs': occurs,
666 'trigger_related': related,
667 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
669 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
671 new_res_alarm = alarm_ids[0]
672 cr.execute('UPDATE %s ' % model_obj._table + \
673 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
675 (cal_alarm.id, new_res_alarm, data.id))
677 self.do_alarm_unlink(cr, uid, [data.id], model)
681 'description': data.description,
683 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
684 'trigger_related': basic_alarm.trigger_related,
685 'trigger_duration': basic_alarm.trigger_duration,
686 'trigger_occurs': basic_alarm.trigger_occurs,
687 'trigger_interval': basic_alarm.trigger_interval,
688 'duration': basic_alarm.duration,
689 'repeat': basic_alarm.repeat,
691 'event_date': data[date],
693 'model_id': model_id,
696 alarm_id = alarm_obj.create(cr, uid, vals)
697 cr.execute('UPDATE %s ' % model_obj._table + \
698 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
700 ( alarm_id, basic_alarm.id, data.id) )
703 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
705 Delete alarm specified in ids
706 @param cr: the current row, from the database cursor,
707 @param uid: the current user’s ID for security checks,
708 @param ids: List of res alarm’s IDs.
709 @param model: Model name for which alarm is to be cleared.
714 alarm_obj = self.pool.get('calendar.alarm')
715 ir_obj = self.pool.get('ir.model')
716 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
717 model_obj = self.pool.get(model)
718 for datas in model_obj.browse(cr, uid, ids, context=context):
719 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
721 alarm_obj.unlink(cr, uid, alarm_ids)
722 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
723 where id=%%s' % model_obj._table,(datas.id,))
728 class calendar_alarm(osv.osv):
729 _name = 'calendar.alarm'
730 _description = 'Event alarm information'
731 _inherit = 'res.alarm'
735 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
736 'name': fields.char('Summary', size=124, help="""Contains the text to be \
737 used as the message subject for email \
738 or contains the text to be used for display"""),
739 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
740 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
741 required=True, help="Defines the action to be invoked when an alarm is triggered"),
742 'description': fields.text('Description', help='Provides a more complete \
743 description of the calendar component, than that \
744 provided by the "SUMMARY" property'),
745 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
746 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
747 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
748 which is rendered when the alarm is triggered for audio,
749 * File which is intended to be sent as message attachments for email,
750 * Points to a procedure resource, which is invoked when\
751 the alarm is triggered for procedure."""),
752 'res_id': fields.integer('Resource ID'),
753 'model_id': fields.many2one('ir.model', 'Model'),
754 'user_id': fields.many2one('res.users', 'Owner'),
755 'event_date': fields.datetime('Event Date'),
756 'event_end_date': fields.datetime('Event End Date'),
757 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
758 'state':fields.selection([
763 ], 'Status', select=True, readonly=True),
771 def create(self, cr, uid, vals, context=None):
773 Overrides orm create method.
774 @param self: The object pointer
775 @param cr: the current row, from the database cursor,
776 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
777 @param context: A standard dictionary for contextual values
778 @return: new record id for calendar_alarm.
782 event_date = vals.get('event_date', False)
784 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
785 if vals['trigger_interval'] == 'days':
786 delta = timedelta(days=vals['trigger_duration'])
787 if vals['trigger_interval'] == 'hours':
788 delta = timedelta(hours=vals['trigger_duration'])
789 if vals['trigger_interval'] == 'minutes':
790 delta = timedelta(minutes=vals['trigger_duration'])
791 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
792 vals['trigger_date'] = trigger_date
793 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
796 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
798 """Scheduler for event reminder
799 @param self: The object pointer
800 @param cr: the current row, from the database cursor,
801 @param uid: the current user’s ID for security checks,
802 @param ids: List of calendar alarm’s IDs.
803 @param use_new_cursor: False or the dbname
804 @param context: A standard dictionary for contextual values
808 current_datetime = datetime.now()
809 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
813 for alarm in self.browse(cr, uid, alarm_ids, context=context):
814 next_trigger_date = None
816 model_obj = self.pool.get(alarm.model_id.model)
817 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
821 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
822 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
824 trigger_interval = alarm.trigger_interval
825 if trigger_interval == 'days':
826 delta = timedelta(days=alarm.trigger_duration)
827 if trigger_interval == 'hours':
828 delta = timedelta(hours=alarm.trigger_duration)
829 if trigger_interval == 'minutes':
830 delta = timedelta(minutes=alarm.trigger_duration)
831 delta = alarm.trigger_occurs == 'after' and delta or -delta
833 for rdate in recurrent_dates:
834 if rdate + delta > current_datetime:
836 if rdate + delta <= current_datetime:
837 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
838 rest_dates = recurrent_dates[len(re_dates):]
839 next_trigger_date = rest_dates and rest_dates[0] or None
842 re_dates = [alarm.trigger_date]
845 if alarm.action == 'email':
846 sub = '[OpenERP Reminder] %s' % (alarm.name)
858 """ % (alarm.name, alarm.trigger_date, alarm.description, \
859 alarm.user_id.name, alarm.user_id.signature)
860 mail_to = [alarm.user_id.email]
861 for att in alarm.attendee_ids:
862 mail_to.append(att.user_id.email)
869 'email_from': tools.config.get('email_from', mail_to),
871 self.pool.get('mail.mail').create(cr, uid, vals, context=context)
872 if next_trigger_date:
873 update_vals.update({'trigger_date': next_trigger_date})
875 update_vals.update({'state': 'done'})
876 self.write(cr, uid, [alarm.id], update_vals)
882 class calendar_event(osv.osv):
883 _name = "calendar.event"
884 _description = "Calendar Event"
887 def _tz_get(self, cr, uid, context=None):
888 return [(x.lower(), x) for x in pytz.all_timezones]
890 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
891 """Returns duration and/or end date based on values passed
892 @param self: The object pointer
893 @param cr: the current row, from the database cursor,
894 @param uid: the current user’s ID for security checks,
895 @param ids: List of calendar event’s IDs.
896 @param start_date: Starting date
897 @param duration: Duration between start date and end date
898 @param end_date: Ending Datee
899 @param context: A standard dictionary for contextual values
907 if not end_date and not duration:
909 value['duration'] = duration
911 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
912 if allday: # For all day event
914 value['duration'] = duration
915 # change start_date's time to 00:00:00 in the user's timezone
916 user = self.pool.get('res.users').browse(cr, uid, uid)
917 tz = pytz.timezone(user.tz) if user.tz else pytz.utc
918 start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone
919 start = start.replace(hour=0, minute=0, second=0) # change start's time to 00:00:00
920 start = start.astimezone(pytz.utc) # convert start back to utc
921 start_date = start.strftime("%Y-%m-%d %H:%M:%S")
922 value['date'] = start_date
924 if end_date and not duration:
925 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
927 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
928 value['duration'] = round(duration, 2)
930 end = start + timedelta(hours=duration)
931 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
932 elif end_date and duration and not allday:
933 # we have both, keep them synchronized:
934 # set duration based on end_date (arbitrary decision: this avoid
935 # getting dates like 06:31:48 instead of 06:32:00)
936 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
938 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
939 value['duration'] = round(duration, 2)
941 return {'value': value}
943 def unlink_events(self, cr, uid, ids, context=None):
945 This function deletes event which are linked with the event with recurrent_uid
946 (Removes the events which refers to the same UID value)
951 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
952 r_ids = map(lambda x: x[0], cr.fetchall())
953 self.unlink(cr, uid, r_ids, context=context)
956 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
958 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
959 @param self: The object pointer
960 @param cr: the current row, from the database cursor,
961 @param id: List of calendar event's ids.
962 @param context: A standard dictionary for contextual values
963 @return: dictionary of rrule value.
967 if not isinstance(ids, list):
970 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):
972 if datas.get('interval', 0) < 0:
973 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.'))
974 if datas.get('count', 0) < 0:
975 raise osv.except_osv(_('Warning!'), _('Count cannot be negative.'))
976 if datas['recurrency']:
977 result[event] = self.compute_rule_string(datas)
982 def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
983 data = self._get_empty_rrule_data()
985 data['recurrency'] = True
986 for event in self.browse(cr, uid, ids, context=context):
987 rdate = rule_date or event.date
988 update_data = self._parse_rrule(field_value, dict(data), rdate)
989 data.update(update_data)
990 super(calendar_event, obj).write(cr, uid, ids, data, context=context)
995 'id': fields.integer('ID', readonly=True),
996 'sequence': fields.integer('Sequence'),
997 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
998 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,),
999 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}, required=True,),
1000 'create_date': fields.datetime('Created', readonly=True),
1001 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1002 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1003 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1004 ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
1005 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1006 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1007 'Show Time as', states={'done': [('readonly', True)]}),
1008 'base_calendar_url': fields.char('Caldav URL', size=264),
1009 'state': fields.selection([('tentative', 'Tentative'),
1010 ('cancelled', 'Cancelled'),
1011 ('confirmed', 'Confirmed'),
1012 ], 'Status', readonly=True),
1013 'exdate': fields.text('Exception Date/Times', help="This property \
1014 defines the list of date/time exceptions for a recurring calendar component."),
1015 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1016 rule or repeating pattern of time to exclude from the recurring rule."),
1017 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1018 fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
1019 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1020 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1021 ('yearly', 'Yearly'),],
1022 'Recurrency', states={'done': [('readonly', True)]},
1023 help="Let the event automatically repeat at that interval"),
1024 'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]},
1025 help="Set an alarm at this time, before the event occurs" ),
1026 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1027 'recurrent_uid': fields.integer('Recurrent ID'),
1028 'recurrent_id': fields.datetime('Recurrent ID date'),
1029 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1030 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1031 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1032 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1033 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'),
1034 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
1035 'count': fields.integer('Repeat', help="Repeat x times"),
1036 'mo': fields.boolean('Mon'),
1037 'tu': fields.boolean('Tue'),
1038 'we': fields.boolean('Wed'),
1039 'th': fields.boolean('Thu'),
1040 'fr': fields.boolean('Fri'),
1041 'sa': fields.boolean('Sat'),
1042 'su': fields.boolean('Sun'),
1043 'select1': fields.selection([('date', 'Date of month'),
1044 ('day', 'Day of month')], 'Option'),
1045 'day': fields.integer('Date of month'),
1046 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1047 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1048 ('FR', 'Friday'), ('SA', 'Saturday'), \
1049 ('SU', 'Sunday')], 'Weekday'),
1050 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1051 ('3', 'Third'), ('4', 'Fourth'), \
1052 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1053 'month_list': fields.selection(months.items(), 'Month'),
1054 'end_date': fields.date('Repeat Until'),
1055 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1056 'event_id', 'attendee_id', 'Attendees'),
1057 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1058 'active': fields.boolean('Active', help="If the active field is set to \
1059 true, it will allow you to hide the event alarm information without removing it."),
1060 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1063 def default_organizer(self, cr, uid, context=None):
1064 user_pool = self.pool.get('res.users')
1065 user = user_pool.browse(cr, uid, uid, context=context)
1068 res += " <%s>" %(user.email)
1072 'end_type' : 'count',
1074 'rrule_type' : 'none',
1075 'state': 'tentative',
1081 'user_id': lambda self, cr, uid, ctx: uid,
1082 'organizer': default_organizer,
1085 def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None):
1086 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1087 This method gives ids of dates that comes between start date and end date of calendar views
1088 @param self: The object pointer
1089 @param cr: the current row, from the database cursor,
1090 @param uid: the current user’s ID for security checks,
1091 @param limit: The Number of Results to Return """
1096 for data in super(calendar_event, self).read(cr, uid, select, context=context):
1097 if not data['rrule']:
1098 result.append(data['id'])
1100 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1101 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1103 if not data['rrule']:
1106 exdate = data['exdate'] and data['exdate'].split(',') or []
1107 rrule_str = data['rrule']
1109 rrule_until_date = False
1111 for rule in rrule_str.split(';'):
1112 name, value = rule.split('=')
1115 value = parser.parse(value)
1116 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1117 value = value.strftime("%Y%m%d%H%M%S")
1118 new_rule = '%s=%s' % (name, value)
1119 new_rrule_str.append(new_rule)
1120 new_rrule_str = ';'.join(new_rrule_str)
1121 rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
1122 for r_date in rdates:
1125 if arg[0] in ('date', 'date_deadline'):
1127 ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
1129 ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
1131 ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
1133 ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
1135 ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
1138 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1139 result.append(idval)
1141 if isinstance(select, (str, int, long)):
1142 return ids and ids[0] or False
1144 ids = list(set(result))
1147 def compute_rule_string(self, datas):
1149 Compute rule string according to value type RECUR of iCalendar from the values given.
1150 @param self: the object pointer
1151 @param datas: dictionary of freq and interval value.
1153 def get_week_string(freq, datas):
1154 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1155 if freq == 'weekly':
1156 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1158 return ';BYDAY=' + ','.join(byday)
1161 def get_month_string(freq, datas):
1162 if freq == 'monthly':
1163 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1164 raise osv.except_osv(_('Error!'), ("Please select a proper day of the month."))
1166 if datas.get('select1')=='day':
1167 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1168 elif datas.get('select1')=='date':
1169 return ';BYMONTHDAY=' + str(datas.get('day'))
1172 def get_end_date(datas):
1173 if datas.get('end_date'):
1174 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1176 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1177 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1179 freq=datas.get('rrule_type')
1183 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1185 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1187 def _get_empty_rrule_data(self):
1190 'recurrency' : False,
1192 'rrule_type' : False,
1209 #def _write_rrule(self, cr, uid, ids, field_value, rule_date=False, context=None):
1210 # data = self._get_empty_rrule_data()
1213 # data['recurrency'] = True
1214 # for event in self.browse(cr, uid, ids, context=context):
1215 # rdate = rule_date or event.date
1216 # update_data = self._parse_rrule(field_value, dict(data), rdate)
1217 # data.update(update_data)
1219 # self.write(cr, uid, event.id, data, context=context)
1222 def _parse_rrule(self, rule, data, date_start):
1223 day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1224 rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1225 r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1227 if r._freq > 0 and r._freq < 4:
1228 data['rrule_type'] = rrule_type[r._freq]
1230 data['count'] = r._count
1231 data['interval'] = r._interval
1232 data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1235 for i in xrange(0,7):
1236 if i in r._byweekday:
1237 data[day_list[i]] = True
1238 data['rrule_type'] = 'weekly'
1239 #repeat monthly bynweekday ((weekday, weeknumber), )
1241 data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1242 data['byday'] = r._bynweekday[0][1]
1243 data['select1'] = 'day'
1244 data['rrule_type'] = 'monthly'
1247 data['day'] = r._bymonthday[0]
1248 data['select1'] = 'date'
1249 data['rrule_type'] = 'monthly'
1251 #yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1253 data['interval'] = data['interval'] * 12
1255 #FIXEME handle forever case
1257 #in case of repeat for ever that we do not support right now
1258 if not (data.get('count') or data.get('end_date')):
1260 if data.get('count'):
1261 data['end_type'] = 'count'
1263 data['end_type'] = 'end_date'
1266 def remove_virtual_id(self, ids):
1267 if isinstance(ids, (str, int, long)):
1268 return base_calendar_id2real_id(ids)
1270 if isinstance(ids, (list, tuple)):
1273 res.append(base_calendar_id2real_id(id))
1276 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1277 context = context or {}
1278 args_without_date = []
1283 new_id = self.remove_virtual_id(arg[2])
1284 new_arg = (arg[0], arg[1], new_id)
1285 args_without_date.append(new_arg)
1286 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1287 args_without_date.append(arg)
1289 if context.get('virtual_id', True):
1290 args_without_date.append('|')
1291 args_without_date.append(arg)
1292 if context.get('virtual_id', True):
1293 args_without_date.append(('recurrency','=',1))
1294 filter_date.append(arg)
1296 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1297 0, 0, order, context, count=False)
1298 if context.get('virtual_id', True):
1299 res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
1304 return res[offset:offset+limit]
1308 def _get_data(self, cr, uid, id, context=None):
1309 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1312 def need_to_update(self, event_id, vals):
1313 split_id = str(event_id).split("-")
1314 if len(split_id) < 2:
1317 date_start = vals.get('date', '')
1319 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1320 return date_start == split_id[1]
1325 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1326 context = context or {}
1327 if isinstance(ids, (str, int, long)):
1331 # Special write of complex IDS
1332 for event_id in ids[:]:
1333 if len(str(event_id).split('-')) == 1:
1335 ids.remove(event_id)
1336 real_event_id = base_calendar_id2real_id(event_id)
1337 if not vals.get('recurrency', True):
1338 ids.append(real_event_id)
1341 #if edit one instance of a reccurrent id
1342 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1343 'rrule', 'duration', 'exdate'])
1344 if data.get('rrule'):
1347 'recurrent_uid': real_event_id,
1348 'recurrent_id': data.get('date'),
1349 'rrule_type': 'none',
1351 'recurrency' : False,
1354 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1356 date_new = event_id.split('-')[1]
1357 date_new = time.strftime("%Y%m%dT%H%M%S", \
1358 time.strptime(date_new, "%Y%m%d%H%M%S"))
1359 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1360 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1362 context.update({'active_id': new_id, 'active_ids': [new_id]})
1365 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1366 vals['vtimezone'] = vals['vtimezone'][40:]
1368 res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
1370 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1371 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1372 alarm_obj = self.pool.get('res.alarm')
1373 alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context)
1374 return res or True and False
1376 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1380 if 'date' in groupby:
1381 raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.'))
1382 virtual_id = context.get('virtual_id', True)
1383 context.update({'virtual_id': False})
1384 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1386 #remove the count, since the value is not consistent with the result of the search when expand the group
1387 for groupname in groupby:
1388 if re.get(groupname + "_count"):
1389 del re[groupname + "_count"]
1390 re.get('__context', {}).update({'virtual_id' : virtual_id})
1393 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1394 # FIXME This whole id mangling has to go!
1397 fields2 = fields and fields[:] or None
1399 EXTRAFIELDS = ('class','user_id','duration')
1400 for f in EXTRAFIELDS:
1401 if fields and (f not in fields):
1404 if isinstance(ids, (str, int, long)):
1408 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1411 real_data = super(calendar_event, self).read(cr, uid,
1412 [real_id for base_calendar_id, real_id in select],
1413 fields=fields2, context=context, load=load)
1414 real_data = dict(zip([x['id'] for x in real_data], real_data))
1416 for base_calendar_id, real_id in select:
1417 res = real_data[real_id].copy()
1418 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1419 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1421 res['date_deadline'] = ls[2]
1422 res['id'] = base_calendar_id
1428 user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id']
1431 if r['class']=='private':
1433 if f not in ('id','date','date_deadline','duration','user_id','state'):
1439 for k in EXTRAFIELDS:
1440 if (k in r) and ((not fields) or (k not in fields)):
1442 if isinstance(ids, (str, int, long)):
1443 return result and result[0] or False
1446 def copy(self, cr, uid, id, default=None, context=None):
1450 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1451 alarm_obj = self.pool.get('res.alarm')
1452 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1455 def unlink(self, cr, uid, ids, context=None):
1456 if not isinstance(ids, list):
1459 attendee_obj=self.pool.get('calendar.attendee')
1460 for event_id in ids[:]:
1461 if len(str(event_id).split('-')) == 1:
1464 real_event_id = base_calendar_id2real_id(event_id)
1465 data = self.read(cr, uid, real_event_id, ['exdate'], context=context)
1466 date_new = event_id.split('-')[1]
1467 date_new = time.strftime("%Y%m%dT%H%M%S", \
1468 time.strptime(date_new, "%Y%m%d%H%M%S"))
1469 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1470 self.write(cr, uid, [real_event_id], {'exdate': exdate})
1471 ids.remove(event_id)
1472 for event in self.browse(cr, uid, ids, context=context):
1473 if event.attendee_ids:
1474 attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context)
1476 res = super(calendar_event, self).unlink(cr, uid, ids, context=context)
1477 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name)
1478 self.unlink_events(cr, uid, ids, context=context)
1482 def create(self, cr, uid, vals, context=None):
1486 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1487 vals['vtimezone'] = vals['vtimezone'][40:]
1489 #updated_vals = self.onchange_dates(cr, uid, [],
1490 # vals.get('date', False),
1491 # vals.get('duration', False),
1492 # vals.get('date_deadline', False),
1493 # vals.get('allday', False),
1495 #vals.update(updated_vals.get('value', {}))
1497 res = super(calendar_event, self).create(cr, uid, vals, context)
1498 alarm_obj = self.pool.get('res.alarm')
1499 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1502 def do_tentative(self, cr, uid, ids, context=None, *args):
1503 """ Makes event invitation as Tentative
1504 @param self: The object pointer
1505 @param cr: the current row, from the database cursor,
1506 @param uid: the current user’s ID for security checks,
1507 @param ids: List of Event IDs
1508 @param *args: Get Tupple value
1509 @param context: A standard dictionary for contextual values
1511 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1513 def do_cancel(self, cr, uid, ids, context=None, *args):
1514 """ Makes event invitation as Tentative
1515 @param self: The object pointer
1516 @param cr: the current row, from the database cursor,
1517 @param uid: the current user’s ID for security checks,
1518 @param ids: List of Event IDs
1519 @param *args: Get Tupple value
1520 @param context: A standard dictionary for contextual values
1522 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1524 def do_confirm(self, cr, uid, ids, context=None, *args):
1525 """ Makes event invitation as Tentative
1526 @param self: The object pointer
1527 @param cr: the current row, from the database cursor,
1528 @param uid: the current user’s ID for security checks,
1529 @param ids: List of Event IDs
1530 @param *args: Get Tupple value
1531 @param context: A standard dictionary for contextual values
1533 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1537 class calendar_todo(osv.osv):
1538 """ Calendar Task """
1540 _name = "calendar.todo"
1541 _inherit = "calendar.event"
1542 _description = "Calendar Task"
1544 def _get_date(self, cr, uid, ids, name, arg, context=None):
1547 @param self: The object pointer
1548 @param cr: the current row, from the database cursor,
1549 @param uid: the current user’s ID for security checks,
1550 @param ids: List of calendar todo's IDs.
1551 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1552 @param context: A standard dictionary for contextual values
1556 for event in self.browse(cr, uid, ids, context=context):
1557 res[event.id] = event.date_start
1560 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1563 @param self: The object pointer
1564 @param cr: the current row, from the database cursor,
1565 @param uid: the current user’s ID for security checks,
1566 @param id: calendar's ID.
1567 @param value: Get Value
1568 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1569 @param context: A standard dictionary for contextual values
1572 assert name == 'date'
1573 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1576 'date': fields.function(_get_date, fnct_inv=_set_date, \
1577 string='Duration', store=True, type='datetime'),
1578 'duration': fields.integer('Duration'),
1587 class ir_values(osv.osv):
1588 _inherit = 'ir.values'
1590 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1591 isobject=False, meta=False, preserve_user=False, company=False):
1594 @param self: The object pointer
1595 @param cr: the current row, from the database cursor,
1596 @param uid: the current user’s ID for security checks,
1597 @param model: Get The Model
1602 if type(data) in (list, tuple):
1603 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1605 new_model.append(data)
1606 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1607 value, replace, isobject, meta, preserve_user, company)
1609 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1610 res_id_req=False, without_user=True, key2_req=True):
1613 @param self: The object pointer
1614 @param cr: the current row, from the database cursor,
1615 @param uid: the current user’s ID for security checks,
1616 @param model: Get The Model
1622 if type(data) in (list, tuple):
1623 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1625 new_model.append(data)
1626 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1627 meta, context, res_id_req, without_user, key2_req)
1631 class ir_model(osv.osv):
1633 _inherit = 'ir.model'
1635 def read(self, cr, uid, ids, fields=None, context=None,
1636 load='_classic_read'):
1638 Overrides orm read method.
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 ids: List of IR Model’s IDs.
1643 @param context: A standard dictionary for contextual values
1645 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1648 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1649 context=context, load=load)
1652 val['id'] = base_calendar_id2real_id(val['id'])
1653 return isinstance(ids, (str, int, long)) and data[0] or data
1657 class virtual_report_spool(web_services.report_spool):
1659 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1662 @param self: The object pointer
1663 @param db: get the current database,
1664 @param uid: the current user’s ID for security checks,
1665 @param context: A standard dictionary for contextual values
1668 if object == 'printscreen.list':
1669 return super(virtual_report_spool, self).exp_report(db, uid, \
1670 object, ids, datas, context)
1673 new_ids.append(base_calendar_id2real_id(id))
1674 if datas.get('id', False):
1675 datas['id'] = base_calendar_id2real_id(datas['id'])
1676 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1678 virtual_report_spool()
1680 class res_users(osv.osv):
1681 _inherit = 'res.users'
1683 def _get_user_avail(self, cr, uid, ids, context=None):
1685 Get User Availability
1686 @param self: The object pointer
1687 @param cr: the current row, from the database cursor,
1688 @param uid: the current user’s ID for security checks,
1689 @param ids: List of res user’s IDs.
1690 @param context: A standard dictionary for contextual values
1693 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1695 attendee_obj = self.pool.get('calendar.attendee')
1696 attendee_ids = attendee_obj.search(cr, uid, [
1697 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1698 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1701 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1702 user_id = attendee_data['user_id']
1704 res.update({user_id:status})
1706 #TOCHECK: Delegated Event
1708 if user_id not in res:
1709 res[user_id] = 'free'
1713 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1715 Get User Availability Function
1716 @param self: The object pointer
1717 @param cr: the current row, from the database cursor,
1718 @param uid: the current user’s ID for security checks,
1719 @param ids: List of res user’s IDs.
1720 @param context: A standard dictionary for contextual values
1723 return self._get_user_avail(cr, uid, ids, context=context)
1726 'availability': fields.function(_get_user_avail_fun, type='selection', \
1727 selection=[('free', 'Free'), ('busy', 'Busy')], \
1728 string='Free/Busy'),
1734 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: