1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from datetime import datetime, timedelta, date
23 from dateutil import parser
24 from dateutil import rrule
25 from dateutil.relativedelta import relativedelta
26 from osv import fields, osv
27 from service import web_services
28 from tools.translate import _
35 1: "January", 2: "February", 3: "March", 4: "April", \
36 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
37 10: "October", 11: "November", 12: "December"
40 def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
42 Get recurrent dates based on Rule string considering exdate and start date
43 @param rrulestring: Rulestring
44 @param exdate: List of exception dates for rrule
45 @param startdate: Startdate for computing recurrent dates
46 @return: List of Recurrent dates
49 val = parser.parse(''.join((re.compile('\d')).findall(date)))
53 startdate = datetime.now()
58 rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
60 datetime_obj = todate(date)
61 rset1._exdate.append(datetime_obj)
64 rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
68 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
70 This function converts virtual event id into real id of actual event
71 @param base_calendar_id: Id of calendar
72 @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
75 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
76 res = base_calendar_id.split('-')
81 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
82 time.strptime(res[1], "%Y%m%d%H%M%S"))
83 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
84 end = start + timedelta(hours=with_date)
85 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
88 return base_calendar_id and int(base_calendar_id) or base_calendar_id
90 def real_id2base_calendar_id(real_id, recurrent_date):
92 Convert real id of record into virtual id using recurrent_date
93 e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
95 @return: real id with recurrent date.
98 if real_id and recurrent_date:
99 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
100 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
101 return '%d-%s' % (real_id, recurrent_date)
104 def _links_get(self, cr, uid, context=None):
107 @param cr: the current row, from the database cursor,
108 @param uid: the current user’s ID for security checks,
109 @param context: A standard dictionary for contextual values
110 @return: list of dictionary which contain object and name and id.
112 obj = self.pool.get('res.request.link')
113 ids = obj.search(cr, uid, [])
114 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
115 return [(r['object'], r['name']) for r in res]
117 html_invitation = """
120 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
121 <title>%(name)s</title>
124 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
125 style="font-family: Arial, Sans-serif; font-size: 14">
127 <td width="100%%">Hello,</td>
130 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
133 <td width="100%%">Below are the details of event:</td>
137 <table cellspacing="0" cellpadding="5" border="0" summary=""
138 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
139 <tr valign="center" align="center">
140 <td bgcolor="DFDFDF">
146 <table cellpadding="8" cellspacing="0" border="0"
147 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
151 <div><b>Start Date</b></div>
154 <td>%(start_date)s</td>
156 <div><b>End Date</b></div>
159 <td width="25%%">%(end_date)s</td>
162 <td><b>Description</b></td>
164 <td colspan="3">%(description)s</td>
168 <div><b>Location</b></div>
171 <td colspan="3">%(location)s</td>
175 <div><b>Event Attendees</b></div>
180 <div>%(attendees)s</div>
188 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
189 style="font-family: Arial, Sans-serif; font-size: 14">
191 <td width="100%%">From:</td>
194 <td width="100%%">%(user)s</td>
197 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
200 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
207 class calendar_attendee(osv.osv):
209 Calendar Attendee Information
211 _name = 'calendar.attendee'
212 _description = 'Attendee information'
217 def _get_address(self, name=None, email=None):
219 Gives email information in ical CAL-ADDRESS type format
220 @param name: Name for CAL-ADDRESS value
221 @param email: Email address for CAL-ADDRESS value
225 return (name or '') + (email and ('MAILTO:' + email) or '')
227 def _compute_data(self, cr, uid, ids, name, arg, context=None):
229 Compute data on function fields for attendee values .
230 @param cr: the current row, from the database cursor,
231 @param uid: the current user’s ID for security checks,
232 @param ids: List of calendar attendee’s IDs.
233 @param name: name of field.
234 @param context: A standard dictionary for contextual values
235 @return: Dictionary of form {id: {'field Name': value'}}.
239 for attdata in self.browse(cr, uid, ids, context=context):
242 if name == 'sent_by':
243 if not attdata.sent_by_uid:
244 result[id][name] = ''
247 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
248 attdata.sent_by_uid.user_email)
252 result[id][name] = attdata.user_id.name
253 elif attdata.partner_address_id:
254 result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
256 result[id][name] = attdata.email or ''
258 if name == 'delegated_to':
260 for child in attdata.child_ids:
262 todata.append('MAILTO:' + child.email)
263 result[id][name] = ', '.join(todata)
265 if name == 'delegated_from':
267 for parent in attdata.parent_ids:
269 fromdata.append('MAILTO:' + parent.email)
270 result[id][name] = ', '.join(fromdata)
272 if name == 'event_date':
274 result[id][name] = attdata.ref.date
276 result[id][name] = False
278 if name == 'event_end_date':
280 result[id][name] = attdata.ref.date_deadline
282 result[id][name] = False
284 if name == 'sent_by_uid':
286 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
288 result[id][name] = uid
290 if name == 'language':
291 user_obj = self.pool.get('res.users')
292 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
293 result[id][name] = lang.replace('_', '-')
297 def _links_get(self, cr, uid, context=None):
299 Get request link for ref field in calendar attendee.
300 @param cr: the current row, from the database cursor,
301 @param uid: the current user’s ID for security checks,
302 @param context: A standard dictionary for contextual values
303 @return: list of dictionary which contain object and name and id.
305 obj = self.pool.get('res.request.link')
306 ids = obj.search(cr, uid, [])
307 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
308 return [(r['object'], r['name']) for r in res]
310 def _lang_get(self, cr, uid, context=None):
312 Get language for language selection field.
313 @param cr: the current row, from the database cursor,
314 @param uid: the current user’s ID for security checks,
315 @param context: A standard dictionary for contextual values
316 @return: list of dictionary which contain code and name and id.
318 obj = self.pool.get('res.lang')
319 ids = obj.search(cr, uid, [])
320 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
321 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
325 'cutype': fields.selection([('individual', 'Individual'), \
326 ('group', 'Group'), ('resource', 'Resource'), \
327 ('room', 'Room'), ('unknown', 'Unknown') ], \
328 'Invite Type', help="Specify the type of Invitation"),
329 'member': fields.char('Member', size=124,
330 help="Indicate the groups that the attendee belongs to"),
331 'role': fields.selection([('req-participant', 'Participation required'), \
332 ('chair', 'Chair Person'), \
333 ('opt-participant', 'Optional Participation'), \
334 ('non-participant', 'For information Purpose')], 'Role', \
335 help='Participation role for the calendar user'),
336 'state': fields.selection([('tentative', 'Tentative'),
337 ('needs-action', 'Needs Action'),
338 ('accepted', 'Accepted'),
339 ('declined', 'Declined'),
340 ('delegated', 'Delegated')], 'State', 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_address_id': fields.many2one('res.partner.address', 'Contact'),
369 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
370 relation='res.partner', string='Partner', help="Partner related to contact"),
371 'email': fields.char('Email', size=124, help="Email of Invited Person"),
372 'event_date': fields.function(_compute_data, string='Event Date', \
373 type="datetime", multi='event_date'),
374 'event_end_date': fields.function(_compute_data, \
375 string='Event End Date', type="datetime", \
376 multi='event_end_date'),
377 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
378 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
382 'state': 'needs-action',
383 'role': 'req-participant',
385 'cutype': 'individual',
388 def copy(self, cr, uid, id, default=None, context=None):
389 raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
391 def get_ics_file(self, cr, uid, event_obj, context=None):
393 Returns iCalendar file for the event invitation
394 @param self: The object pointer
395 @param cr: the current row, from the database cursor,
396 @param uid: the current user’s ID for security checks,
397 @param event_obj: Event object (browse record)
398 @param context: A standard dictionary for contextual values
399 @return: .ics file content
402 def ics_datetime(idate, short=False):
404 if short or len(idate)<=10:
405 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
407 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
411 # FIXME: why isn't this in CalDAV?
415 cal = vobject.iCalendar()
416 event = cal.add('vevent')
417 if not event_obj.date_deadline or not event_obj.date:
418 raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
419 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
420 event.add('dtstart').value = ics_datetime(event_obj.date)
421 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
422 event.add('summary').value = event_obj.name
423 if event_obj.description:
424 event.add('description').value = event_obj.description
425 if event_obj.location:
426 event.add('location').value = event_obj.location
428 event.add('rrule').value = event_obj.rrule
429 if event_obj.organizer:
430 event_org = event.add('organizer')
431 event_org.params['CN'] = [event_obj.organizer]
432 event_org.value = 'MAILTO:' + (event_obj.organizer)
433 elif event_obj.user_id or event_obj.organizer_id:
434 event_org = event.add('organizer')
435 organizer = event_obj.organizer_id
437 organizer = event_obj.user_id
438 event_org.params['CN'] = [organizer.name]
439 event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
441 if event_obj.alarm_id:
442 # computes alarm data
443 valarm = event.add('valarm')
444 alarm_object = self.pool.get('res.alarm')
445 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
446 # Compute trigger data
447 interval = alarm_data['trigger_interval']
448 occurs = alarm_data['trigger_occurs']
449 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
450 or -(alarm_data['trigger_duration'])
451 related = alarm_data['trigger_related']
452 trigger = valarm.add('TRIGGER')
453 trigger.params['related'] = [related.upper()]
454 if interval == 'days':
455 delta = timedelta(days=duration)
456 if interval == 'hours':
457 delta = timedelta(hours=duration)
458 if interval == 'minutes':
459 delta = timedelta(minutes=duration)
460 trigger.value = delta
461 # Compute other details
462 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
464 for attendee in event_obj.attendee_ids:
465 attendee_add = event.add('attendee')
466 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
467 attendee_add.params['ROLE'] = [str(attendee.role)]
468 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
469 attendee_add.value = 'MAILTO:' + (attendee.email or '')
470 res = cal.serialize()
473 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
475 Send mail for event invitation to event attendees.
476 @param cr: the current row, from the database cursor,
477 @param uid: the current user’s ID for security checks,
478 @param ids: List of attendee’s IDs.
479 @param email_from: Email address for user sending the mail
480 @param context: A standard dictionary for contextual values
486 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
487 mail_message = self.pool.get('mail.message')
488 for att in self.browse(cr, uid, ids, context=context):
489 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
490 sign = '<br>'.join(sign and sign.split('\n') or [])
495 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
497 for att2 in self.browse(cr, uid, other_invitation_ids):
498 att_infos.append(((att2.user_id and att2.user_id.name) or \
499 (att2.partner_id and att2.partner_id.name) or \
500 att2.email) + ' - Status: ' + att2.state.title())
501 body_vals = {'name': res_obj.name,
502 'start_date': res_obj.date,
503 'end_date': res_obj.date_deadline or False,
504 'description': res_obj.description or '-',
505 'location': res_obj.location or '-',
506 'attendees': '<br>'.join(att_infos),
507 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
511 body = html_invitation % body_vals
512 if mail_to and email_from:
513 attach = self.get_ics_file(cr, uid, res_obj, context=context)
514 mail_message.schedule_with_attach(cr, uid,
519 attachments=attach and {'invitation.ics': attach} or None,
526 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
528 Make entry on email and availbility on change of user_id field.
529 @param cr: the current row, from the database cursor,
530 @param uid: the current user’s ID for security checks,
531 @param ids: List of calendar attendee’s IDs.
532 @param user_id: Changed value of User id
533 @return: dictionary of value. which put value in email and availability fields.
537 return {'value': {'email': ''}}
538 usr_obj = self.pool.get('res.users')
539 user = usr_obj.browse(cr, uid, user_id, *args)
540 return {'value': {'email': user.user_email, 'availability':user.availability}}
542 def do_tentative(self, cr, uid, ids, context=None, *args):
543 """ Makes event invitation as Tentative
544 @param self: The object pointer
545 @param cr: the current row, from the database cursor,
546 @param uid: the current user’s ID for security checks,
547 @param ids: List of calendar attendee’s IDs
548 @param *args: Get Tupple value
549 @param context: A standard dictionary for contextual values
551 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
553 def do_accept(self, cr, uid, ids, context=None, *args):
555 Update state of invitation as Accepted and
556 if the invited user is other then event user it will make a copy of this event for invited user
557 @param cr: the current row, from the database cursor,
558 @param uid: the current user’s ID for security checks,
559 @param ids: List of calendar attendee’s IDs.
560 @param context: A standard dictionary for contextual values
566 for vals in self.browse(cr, uid, ids, context=context):
567 if vals.ref and vals.ref.user_id:
568 mod_obj = self.pool.get(vals.ref._name)
569 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
570 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
571 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
575 def do_decline(self, cr, uid, ids, context=None, *args):
576 """ Marks event invitation as Declined
577 @param self: The object pointer
578 @param cr: the current row, from the database cursor,
579 @param uid: the current user’s ID for security checks,
580 @param ids: List of calendar attendee’s IDs
581 @param *args: Get Tupple value
582 @param context: A standard dictionary for contextual values """
585 return self.write(cr, uid, ids, {'state': 'declined'}, context)
587 def create(self, cr, uid, vals, context=None):
588 """ Overrides orm create method.
589 @param self: The object pointer
590 @param cr: the current row, from the database cursor,
591 @param uid: the current user’s ID for security checks,
592 @param vals: Get Values
593 @param context: A standard dictionary for contextual values """
597 if not vals.get("email") and vals.get("cn"):
598 cnval = vals.get("cn").split(':')
599 email = filter(lambda x:x.__contains__('@'), cnval)
600 vals['email'] = email and email[0] or ''
601 vals['cn'] = vals.get("cn")
602 res = super(calendar_attendee, self).create(cr, uid, vals, context)
606 class res_alarm(osv.osv):
607 """Resource Alarm """
609 _description = 'Basic Alarm Information'
612 'name':fields.char('Name', size=256, required=True),
613 'trigger_occurs': fields.selection([('before', 'Before'), \
614 ('after', 'After')], \
615 'Triggers', required=True),
616 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
617 ('hours', 'Hours'), \
618 ('days', 'Days')], 'Interval', \
620 'trigger_duration': fields.integer('Duration', required=True),
621 'trigger_related': fields.selection([('start', 'The event starts'), \
622 ('end', 'The event ends')], \
623 'Related to', required=True),
624 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
625 are both optional, but if one occurs, so MUST the other"""),
626 'repeat': fields.integer('Repeat'),
627 'active': fields.boolean('Active', help="If the active field is set to \
628 true, it will allow you to hide the event alarm information without removing it.")
631 'trigger_interval': 'minutes',
632 'trigger_duration': 5,
633 'trigger_occurs': 'before',
634 'trigger_related': 'start',
638 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
640 Create Alarm for event.
641 @param cr: the current row, from the database cursor,
642 @param uid: the current user’s ID for security checks,
643 @param ids: List of res alarm’s IDs.
644 @param model: Model name.
645 @param date: Event date
646 @param context: A standard dictionary for contextual values
651 alarm_obj = self.pool.get('calendar.alarm')
652 res_alarm_obj = self.pool.get('res.alarm')
653 ir_obj = self.pool.get('ir.model')
654 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
656 model_obj = self.pool.get(model)
657 for data in model_obj.browse(cr, uid, ids, context=context):
659 basic_alarm = data.alarm_id
660 cal_alarm = data.base_calendar_alarm_id
661 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
663 # Find for existing res.alarm
664 duration = cal_alarm.trigger_duration
665 interval = cal_alarm.trigger_interval
666 occurs = cal_alarm.trigger_occurs
667 related = cal_alarm.trigger_related
668 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
669 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
672 'trigger_duration': duration,
673 'trigger_interval': interval,
674 'trigger_occurs': occurs,
675 'trigger_related': related,
676 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
678 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
680 new_res_alarm = alarm_ids[0]
681 cr.execute('UPDATE %s ' % model_obj._table + \
682 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
684 (cal_alarm.id, new_res_alarm, data.id))
686 self.do_alarm_unlink(cr, uid, [data.id], model)
690 'description': data.description,
692 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
693 'trigger_related': basic_alarm.trigger_related,
694 'trigger_duration': basic_alarm.trigger_duration,
695 'trigger_occurs': basic_alarm.trigger_occurs,
696 'trigger_interval': basic_alarm.trigger_interval,
697 'duration': basic_alarm.duration,
698 'repeat': basic_alarm.repeat,
700 'event_date': data[date],
702 'model_id': model_id,
705 alarm_id = alarm_obj.create(cr, uid, vals)
706 cr.execute('UPDATE %s ' % model_obj._table + \
707 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
709 ( alarm_id, basic_alarm.id, data.id) )
712 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
714 Delete alarm specified in ids
715 @param cr: the current row, from the database cursor,
716 @param uid: the current user’s ID for security checks,
717 @param ids: List of res alarm’s IDs.
718 @param model: Model name for which alarm is to be cleared.
723 alarm_obj = self.pool.get('calendar.alarm')
724 ir_obj = self.pool.get('ir.model')
725 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
726 model_obj = self.pool.get(model)
727 for datas in model_obj.browse(cr, uid, ids, context=context):
728 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
730 alarm_obj.unlink(cr, uid, alarm_ids)
731 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
732 where id=%%s' % model_obj._table,(datas.id,))
737 class calendar_alarm(osv.osv):
738 _name = 'calendar.alarm'
739 _description = 'Event alarm information'
740 _inherit = 'res.alarm'
744 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
745 'name': fields.char('Summary', size=124, help="""Contains the text to be \
746 used as the message subject for email \
747 or contains the text to be used for display"""),
748 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
749 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
750 required=True, help="Defines the action to be invoked when an alarm is triggered"),
751 'description': fields.text('Description', help='Provides a more complete \
752 description of the calendar component, than that \
753 provided by the "SUMMARY" property'),
754 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
755 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
756 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
757 which is rendered when the alarm is triggered for audio,
758 * File which is intended to be sent as message attachments for email,
759 * Points to a procedure resource, which is invoked when\
760 the alarm is triggered for procedure."""),
761 'res_id': fields.integer('Resource ID'),
762 'model_id': fields.many2one('ir.model', 'Model'),
763 'user_id': fields.many2one('res.users', 'Owner'),
764 'event_date': fields.datetime('Event Date'),
765 'event_end_date': fields.datetime('Event End Date'),
766 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
767 'state':fields.selection([
772 ], 'State', select=True, readonly=True),
780 def create(self, cr, uid, vals, context=None):
782 Overrides orm create method.
783 @param self: The object pointer
784 @param cr: the current row, from the database cursor,
785 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
786 @param context: A standard dictionary for contextual values
787 @return: new record id for calendar_alarm.
791 event_date = vals.get('event_date', False)
793 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
794 if vals['trigger_interval'] == 'days':
795 delta = timedelta(days=vals['trigger_duration'])
796 if vals['trigger_interval'] == 'hours':
797 delta = timedelta(hours=vals['trigger_duration'])
798 if vals['trigger_interval'] == 'minutes':
799 delta = timedelta(minutes=vals['trigger_duration'])
800 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
801 vals['trigger_date'] = trigger_date
802 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
805 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
807 """Scheduler for event reminder
808 @param self: The object pointer
809 @param cr: the current row, from the database cursor,
810 @param uid: the current user’s ID for security checks,
811 @param ids: List of calendar alarm’s IDs.
812 @param use_new_cursor: False or the dbname
813 @param context: A standard dictionary for contextual values
817 mail_message = self.pool.get('mail.message')
818 current_datetime = datetime.now()
819 request_obj = self.pool.get('res.request')
820 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
824 for alarm in self.browse(cr, uid, alarm_ids, context=context):
825 next_trigger_date = None
827 model_obj = self.pool.get(alarm.model_id.model)
828 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
832 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
833 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
835 trigger_interval = alarm.trigger_interval
836 if trigger_interval == 'days':
837 delta = timedelta(days=alarm.trigger_duration)
838 if trigger_interval == 'hours':
839 delta = timedelta(hours=alarm.trigger_duration)
840 if trigger_interval == 'minutes':
841 delta = timedelta(minutes=alarm.trigger_duration)
842 delta = alarm.trigger_occurs == 'after' and delta or -delta
844 for rdate in recurrent_dates:
845 if rdate + delta > current_datetime:
847 if rdate + delta <= current_datetime:
848 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
849 rest_dates = recurrent_dates[len(re_dates):]
850 next_trigger_date = rest_dates and rest_dates[0] or None
853 re_dates = [alarm.trigger_date]
855 for r_date in re_dates:
856 ref = alarm.model_id.model + ',' + str(alarm.res_id)
858 # search for alreay sent requests
859 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
862 if alarm.action == 'display':
865 'act_from': alarm.user_id.id,
866 'act_to': alarm.user_id.id,
867 'body': alarm.description,
868 'trigger_date': r_date,
871 request_id = request_obj.create(cr, uid, value)
872 request_ids = [request_id]
873 for attendee in res_obj.attendee_ids:
875 value['act_to'] = attendee.user_id.id
876 request_id = request_obj.create(cr, uid, value)
877 request_ids.append(request_id)
878 request_obj.request_send(cr, uid, request_ids)
880 if alarm.action == 'email':
881 sub = '[Openobject Reminder] %s' % (alarm.name)
893 """ % (alarm.name, alarm.trigger_date, alarm.description, \
894 alarm.user_id.name, alarm.user_id.signature)
895 mail_to = [alarm.user_id.user_email]
896 for att in alarm.attendee_ids:
897 mail_to.append(att.user_id.user_email)
899 mail_message.schedule_with_attach(cr, uid,
900 tools.config.get('email_from', False),
906 if next_trigger_date:
907 update_vals.update({'trigger_date': next_trigger_date})
909 update_vals.update({'state': 'done'})
910 self.write(cr, uid, [alarm.id], update_vals)
916 class calendar_event(osv.osv):
917 _name = "calendar.event"
918 _description = "Calendar Event"
921 def _tz_get(self, cr, uid, context=None):
922 return [(x.lower(), x) for x in pytz.all_timezones]
924 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
925 """Returns duration and/or end date based on values passed
926 @param self: The object pointer
927 @param cr: the current row, from the database cursor,
928 @param uid: the current user’s ID for security checks,
929 @param ids: List of calendar event’s IDs.
930 @param start_date: Starting date
931 @param duration: Duration between start date and end date
932 @param end_date: Ending Datee
933 @param context: A standard dictionary for contextual values
941 if not end_date and not duration:
943 value['duration'] = duration
945 if allday: # For all day event
946 value = {'duration': 24.0}
949 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
950 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
951 value['date'] = start_date
954 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
955 if end_date and not duration:
956 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
958 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
959 value['duration'] = round(duration, 2)
961 end = start + timedelta(hours=duration)
962 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
963 elif end_date and duration and not allday:
964 # we have both, keep them synchronized:
965 # set duration based on end_date (arbitrary decision: this avoid
966 # getting dates like 06:31:48 instead of 06:32:00)
967 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
969 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
970 value['duration'] = round(duration, 2)
972 return {'value': value}
974 def unlink_events(self, cr, uid, ids, context=None):
976 This function deletes event which are linked with the event with recurrent_uid
977 (Removes the events which refers to the same UID value)
982 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
983 r_ids = map(lambda x: x[0], cr.fetchall())
984 self.unlink(cr, uid, r_ids, context=context)
987 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
989 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
990 @param self: The object pointer
991 @param cr: the current row, from the database cursor,
992 @param id: List of calendar event's ids.
993 @param context: A standard dictionary for contextual values
994 @return: dictionary of rrule value.
998 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):
1000 if datas.get('interval', 0) < 0:
1001 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative'))
1002 if datas.get('count', 0) < 0:
1003 raise osv.except_osv(_('Warning!'), _('Count cannot be negative'))
1004 result[event] = self.compute_rule_string(datas)
1008 'id': fields.integer('ID'),
1009 'sequence': fields.integer('Sequence'),
1010 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1011 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1012 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1013 'create_date': fields.datetime('Created', readonly=True),
1014 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1015 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1016 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1017 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1018 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1019 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1020 'Show as', states={'done': [('readonly', True)]}),
1021 'base_calendar_url': fields.char('Caldav URL', size=264),
1022 'state': fields.selection([('tentative', 'Tentative'),
1023 ('confirmed', 'Confirmed'),
1024 ('cancelled', 'Cancelled')], 'State', readonly=True),
1025 'exdate': fields.text('Exception Date/Times', help="This property \
1026 defines the list of date/time exceptions for a recurring calendar component."),
1027 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1028 rule or repeating pattern of time to exclude from the recurring rule."),
1029 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1030 store=True, string='Recurrent Rule'),
1031 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1032 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1033 ('yearly', 'Yearly'),],
1034 'Recurrency', states={'done': [('readonly', True)]},
1035 help="Let the event automatically repeat at that interval"),
1036 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1037 help="Set an alarm at this time, before the event occurs" ),
1038 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1039 'recurrent_uid': fields.integer('Recurrent ID'),
1040 'recurrent_id': fields.datetime('Recurrent ID date'),
1041 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1042 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1043 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1044 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1045 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence termination'),
1046 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1047 'count': fields.integer('Repeat', help="Repeat x times"),
1048 'mo': fields.boolean('Mon'),
1049 'tu': fields.boolean('Tue'),
1050 'we': fields.boolean('Wed'),
1051 'th': fields.boolean('Thu'),
1052 'fr': fields.boolean('Fri'),
1053 'sa': fields.boolean('Sat'),
1054 'su': fields.boolean('Sun'),
1055 'select1': fields.selection([('date', 'Date of month'),
1056 ('day', 'Day of month')], 'Option'),
1057 'day': fields.integer('Date of month'),
1058 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1059 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1060 ('FR', 'Friday'), ('SA', 'Saturday'), \
1061 ('SU', 'Sunday')], 'Weekday'),
1062 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1063 ('3', 'Third'), ('4', 'Fourth'), \
1064 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1065 'month_list': fields.selection(months.items(), 'Month'),
1066 'end_date': fields.date('Repeat Until'),
1067 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1068 'event_id', 'attendee_id', 'Attendees'),
1069 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1070 'active': fields.boolean('Active', help="If the active field is set to \
1071 true, it will allow you to hide the event alarm information without removing it."),
1072 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1073 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1075 def default_organizer(self, cr, uid, context=None):
1076 user_pool = self.pool.get('res.users')
1077 user = user_pool.browse(cr, uid, uid, context=context)
1080 res += " <%s>" %(user.user_email)
1084 'end_type' : 'count',
1086 'rrule_type' : 'none',
1087 'state': 'tentative',
1093 'user_id': lambda self, cr, uid, ctx: uid,
1094 'organizer': default_organizer,
1098 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
1099 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1100 This method gives ids of dates that comes between start date and end date of calendar views
1101 @param self: The object pointer
1102 @param cr: the current row, from the database cursor,
1103 @param uid: the current user’s ID for security checks,
1104 @param base_start_date: Get Start Date
1105 @param base_until_date: Get End Date
1106 @param limit: The Number of Results to Return """
1110 virtual_id = context and context.get('virtual_id', False) or False
1112 if isinstance(select, (str, int, long)):
1117 if ids and virtual_id:
1118 for data in super(calendar_event, self).read(cr, uid, ids, context=context):
1119 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1120 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1121 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1122 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1124 if not data['rrule']:
1125 if start_date and (event_date < start_date):
1127 if until_date and (event_date > until_date):
1130 result.append(idval)
1132 start_date = event_date
1133 exdate = data['exdate'] and data['exdate'].split(',') or []
1134 rrule_str = data['rrule']
1136 rrule_until_date = False
1138 for rule in rrule_str.split(';'):
1139 name, value = rule.split('=')
1142 value = parser.parse(value)
1143 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1144 if until_date and until_date >= rrule_until_date:
1145 until_date = rrule_until_date
1147 value = until_date.strftime("%Y%m%d%H%M%S")
1149 value = value.strftime("%Y%m%d%H%M%S")
1150 new_rule = '%s=%s' % (name, value)
1151 new_rrule_str.append(new_rule)
1152 if not is_until and until_date:
1153 value = until_date.strftime("%Y%m%d%H%M%S")
1155 new_rule = '%s=%s' % (name, value)
1156 new_rrule_str.append(new_rule)
1157 new_rrule_str = ';'.join(new_rrule_str)
1158 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1160 for r_date in rdates:
1161 if start_date and r_date < start_date:
1163 if until_date and r_date > until_date:
1165 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1166 result.append(idval)
1169 ids = list(set(result))
1170 if isinstance(select, (str, int, long)):
1171 return ids and ids[0] or False
1174 def compute_rule_string(self, datas):
1176 Compute rule string according to value type RECUR of iCalendar from the values given.
1177 @param self: the object pointer
1178 @param datas: dictionary of freq and interval value.
1181 def get_week_string(freq, datas):
1182 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1183 if freq == 'weekly':
1184 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1186 return ';BYDAY=' + ','.join(byday)
1189 def get_month_string(freq, datas):
1190 if freq == 'monthly':
1191 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1192 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1194 if datas.get('select1')=='day':
1195 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1196 elif datas.get('select1')=='date':
1197 return ';BYMONTHDAY=' + str(datas.get('day'))
1200 def get_end_date(datas):
1201 if datas.get('end_date'):
1202 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1204 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1205 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1207 freq=datas.get('rrule_type')
1211 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1213 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1215 def remove_virtual_id(self, ids):
1216 if isinstance(ids, (str, int)):
1217 return base_calendar_id2real_id(ids)
1219 if isinstance(ids, (list, tuple)):
1222 res.append(base_calendar_id2real_id(id))
1225 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1226 args_without_date = []
1232 new_id = self.remove_virtual_id(arg[2])
1233 new_arg = (arg[0], arg[1], new_id)
1234 args_without_date.append(new_arg)
1235 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1236 args_without_date.append(arg)
1238 if arg[1] in ('>', '>='):
1242 elif arg[1] in ('<', '<='):
1247 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1248 0, 0, order, context, count=False)
1249 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
1253 return res[offset:offset+limit]
1257 def get_edit_all(self, cr, uid, id, vals=None):
1259 return true if we have to edit all meeting from the same recurrent
1260 or only on occurency
1262 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1263 if(vals and 'edit_all' in vals): #we jsut check edit_all
1264 return vals['edit_all']
1265 else: #it's a recurrent event and edit_all is already check
1266 return meeting['recurrency'] and meeting['edit_all']
1268 def _get_data(self, cr, uid, id, context=None):
1269 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1272 def need_to_update(self, event_id, vals):
1273 split_id = str(event_id).split("-")
1274 if len(split_id) < 2:
1277 date_start = vals.get('date', '')
1279 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1280 return date_start == split_id[1]
1285 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1288 if isinstance(ids, (str, int, long)):
1294 for event_id in select:
1295 real_event_id = base_calendar_id2real_id(event_id)
1296 edit_all = self.get_edit_all(cr, uid, event_id, vals=vals)
1298 if self.need_to_update(event_id, vals):
1299 res = self._get_data(cr, uid, real_event_id, context=context)
1301 event_id = real_event_id
1303 #if edit one instance of a reccurrent id
1304 if len(str(event_id).split('-')) > 1 and not edit_all:
1305 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1306 'rrule', 'duration', 'exdate'])
1307 if data.get('rrule'):
1310 'recurrent_uid': real_event_id,
1311 'recurrent_id': data.get('date'),
1312 'rrule_type': 'none',
1315 'recurrency' : False,
1318 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1320 date_new = event_id.split('-')[1]
1321 date_new = time.strftime("%Y%m%dT%H%M%S", \
1322 time.strptime(date_new, "%Y%m%d%H%M%S"))
1323 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1324 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1326 context.update({'active_id': new_id, 'active_ids': [new_id]})
1328 if not real_event_id in new_ids:
1329 new_ids.append(real_event_id)
1331 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1332 vals['vtimezone'] = vals['vtimezone'][40:]
1334 updated_vals = self.onchange_dates(cr, uid, new_ids,
1335 vals.get('date', False),
1336 vals.get('duration', False),
1337 vals.get('date_deadline', False),
1338 vals.get('allday', False),
1340 vals.update(updated_vals.get('value', {}))
1342 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1344 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1345 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1346 # change alarm details
1347 alarm_obj = self.pool.get('res.alarm')
1348 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1349 return res or True and False
1351 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1352 if isinstance(ids, (str, int, long)):
1356 select = map(lambda x: base_calendar_id2real_id(x), select)
1357 res = super(calendar_event, self).browse(cr, uid, select, context, \
1358 list_class, fields_process)
1359 if isinstance(ids, (str, int, long)):
1360 return res and res[0] or False
1364 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1368 if 'date' in groupby:
1369 raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1370 virtual_id = context.get('virtual_id', False)
1371 context.update({'virtual_id': False})
1372 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1374 #remove the count, since the value is not consistent with the result of the search when expand the group
1375 for groupname in groupby:
1376 if re.get(groupname + "_count"):
1377 del re[groupname + "_count"]
1378 re.get('__context').update({'virtual_id' : virtual_id})
1381 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1382 # FIXME This whole id mangling has to go!
1388 if isinstance(ids, (str, int, long)):
1392 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1394 if fields and 'date' not in fields:
1395 fields.append('date')
1396 if fields and 'duration' not in fields:
1397 fields.append('duration')
1400 for base_calendar_id, real_id in select:
1401 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1402 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1406 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1407 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1409 res['date_deadline'] = ls[2]
1410 res['id'] = base_calendar_id
1413 if isinstance(ids, (str, int, long)):
1414 return result and result[0] or False
1418 def copy(self, cr, uid, id, default=None, context=None):
1422 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1423 alarm_obj = self.pool.get('res.alarm')
1424 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1428 def unlink(self, cr, uid, ids, context=None):
1429 if not isinstance(ids, list):
1434 data_list = self.read(cr, uid, [id], ['date', 'rrule', 'exdate'], context=context)
1435 if len(data_list) < 1:
1437 event_data = data_list[0]
1438 event_id = event_data['id']
1440 if self.get_edit_all(cr, uid, event_id, vals=None):
1441 event_id = base_calendar_id2real_id(event_id)
1443 if isinstance(event_id, (int, long)):
1444 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1445 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1446 self.unlink_events(cr, uid, [event_id], context=context)
1448 str_event, date_new = event_id.split('-')
1449 event_id = int(str_event)
1450 if event_data['rrule']:
1451 # Remove one of the recurrent event
1452 date_new = time.strftime("%Y%m%dT%H%M%S", \
1453 time.strptime(date_new, "%Y%m%d%H%M%S"))
1454 exdate = (event_data['exdate'] and (event_data['exdate'] + ',') or '') + date_new
1455 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1457 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1458 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1459 self.unlink_events(cr, uid, [event_id], context=context)
1462 def create(self, cr, uid, vals, context=None):
1466 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1467 vals['vtimezone'] = vals['vtimezone'][40:]
1469 updated_vals = self.onchange_dates(cr, uid, [],
1470 vals.get('date', False),
1471 vals.get('duration', False),
1472 vals.get('date_deadline', False),
1473 vals.get('allday', False),
1475 vals.update(updated_vals.get('value', {}))
1476 res = super(calendar_event, self).create(cr, uid, vals, context)
1477 alarm_obj = self.pool.get('res.alarm')
1478 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1484 def do_tentative(self, cr, uid, ids, context=None, *args):
1485 """ Makes event invitation as Tentative
1486 @param self: The object pointer
1487 @param cr: the current row, from the database cursor,
1488 @param uid: the current user’s ID for security checks,
1489 @param ids: List of Event IDs
1490 @param *args: Get Tupple value
1491 @param context: A standard dictionary for contextual values
1493 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1495 def do_cancel(self, cr, uid, ids, context=None, *args):
1496 """ Makes event invitation as Tentative
1497 @param self: The object pointer
1498 @param cr: the current row, from the database cursor,
1499 @param uid: the current user’s ID for security checks,
1500 @param ids: List of Event IDs
1501 @param *args: Get Tupple value
1502 @param context: A standard dictionary for contextual values
1504 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1506 def do_confirm(self, cr, uid, ids, context=None, *args):
1507 """ Makes event invitation as Tentative
1508 @param self: The object pointer
1509 @param cr: the current row, from the database cursor,
1510 @param uid: the current user’s ID for security checks,
1511 @param ids: List of Event IDs
1512 @param *args: Get Tupple value
1513 @param context: A standard dictionary for contextual values
1515 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1519 class calendar_todo(osv.osv):
1520 """ Calendar Task """
1522 _name = "calendar.todo"
1523 _inherit = "calendar.event"
1524 _description = "Calendar Task"
1526 def _get_date(self, cr, uid, ids, name, arg, context=None):
1529 @param self: The object pointer
1530 @param cr: the current row, from the database cursor,
1531 @param uid: the current user’s ID for security checks,
1532 @param ids: List of calendar todo's IDs.
1533 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1534 @param context: A standard dictionary for contextual values
1538 for event in self.browse(cr, uid, ids, context=context):
1539 res[event.id] = event.date_start
1542 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1545 @param self: The object pointer
1546 @param cr: the current row, from the database cursor,
1547 @param uid: the current user’s ID for security checks,
1548 @param id: calendar's ID.
1549 @param value: Get Value
1550 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1551 @param context: A standard dictionary for contextual values
1554 assert name == 'date'
1555 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1558 'date': fields.function(_get_date, fnct_inv=_set_date, \
1559 string='Duration', store=True, type='datetime'),
1560 'duration': fields.integer('Duration'),
1568 class ir_attachment(osv.osv):
1569 _name = 'ir.attachment'
1570 _inherit = 'ir.attachment'
1572 def search_count(self, cr, user, args, context=None):
1574 @param self: The object pointer
1575 @param cr: the current row, from the database cursor,
1576 @param user: the current user’s ID for security checks,
1577 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1578 @param context: A standard dictionary for contextual values
1583 args1.append(map(lambda x:str(x).split('-')[0], arg))
1584 return super(ir_attachment, self).search_count(cr, user, args1, context)
1588 def create(self, cr, uid, vals, context=None):
1590 id = context.get('default_res_id', False)
1591 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1592 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1594 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1595 context=None, count=False):
1597 @param self: The object pointer
1598 @param cr: the current row, from the database cursor,
1599 @param uid: the current user’s ID for security checks,
1600 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1601 @param offset: The Number of Results to pass,
1602 @param limit: The Number of Results to Return,
1603 @param context: A standard dictionary for contextual values
1607 for i, arg in enumerate(new_args):
1608 if arg[0] == 'res_id':
1609 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1611 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1612 limit=limit, order=order, context=context, count=False)
1615 class ir_values(osv.osv):
1616 _inherit = 'ir.values'
1618 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1619 isobject=False, meta=False, preserve_user=False, company=False):
1622 @param self: The object pointer
1623 @param cr: the current row, from the database cursor,
1624 @param uid: the current user’s ID for security checks,
1625 @param model: Get The Model
1630 if type(data) in (list, tuple):
1631 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1633 new_model.append(data)
1634 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1635 value, replace, isobject, meta, preserve_user, company)
1637 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1638 res_id_req=False, without_user=True, key2_req=True):
1641 @param self: The object pointer
1642 @param cr: the current row, from the database cursor,
1643 @param uid: the current user’s ID for security checks,
1644 @param model: Get The Model
1650 if type(data) in (list, tuple):
1651 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1653 new_model.append(data)
1654 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1655 meta, context, res_id_req, without_user, key2_req)
1659 class ir_model(osv.osv):
1661 _inherit = 'ir.model'
1663 def read(self, cr, uid, ids, fields=None, context=None,
1664 load='_classic_read'):
1666 Overrides orm read method.
1667 @param self: The object pointer
1668 @param cr: the current row, from the database cursor,
1669 @param uid: the current user’s ID for security checks,
1670 @param ids: List of IR Model’s IDs.
1671 @param context: A standard dictionary for contextual values
1673 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1676 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1677 context=context, load=load)
1680 val['id'] = base_calendar_id2real_id(val['id'])
1681 return isinstance(ids, (str, int, long)) and data[0] or data
1685 class virtual_report_spool(web_services.report_spool):
1687 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1690 @param self: The object pointer
1691 @param db: get the current database,
1692 @param uid: the current user’s ID for security checks,
1693 @param context: A standard dictionary for contextual values
1696 if object == 'printscreen.list':
1697 return super(virtual_report_spool, self).exp_report(db, uid, \
1698 object, ids, datas, context)
1701 new_ids.append(base_calendar_id2real_id(id))
1702 if datas.get('id', False):
1703 datas['id'] = base_calendar_id2real_id(datas['id'])
1704 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1706 virtual_report_spool()
1708 class res_users(osv.osv):
1709 _inherit = 'res.users'
1711 def _get_user_avail(self, cr, uid, ids, context=None):
1713 Get User Availability
1714 @param self: The object pointer
1715 @param cr: the current row, from the database cursor,
1716 @param uid: the current user’s ID for security checks,
1717 @param ids: List of res user’s IDs.
1718 @param context: A standard dictionary for contextual values
1721 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1723 attendee_obj = self.pool.get('calendar.attendee')
1724 attendee_ids = attendee_obj.search(cr, uid, [
1725 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1726 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1729 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1730 user_id = attendee_data['user_id']
1732 res.update({user_id:status})
1734 #TOCHECK: Delegated Event
1736 if user_id not in res:
1737 res[user_id] = 'free'
1741 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1743 Get User Availability Function
1744 @param self: The object pointer
1745 @param cr: the current row, from the database cursor,
1746 @param uid: the current user’s ID for security checks,
1747 @param ids: List of res user’s IDs.
1748 @param context: A standard dictionary for contextual values
1751 return self._get_user_avail(cr, uid, ids, context=context)
1754 'availability': fields.function(_get_user_avail_fun, type='selection', \
1755 selection=[('free', 'Free'), ('busy', 'Busy')], \
1756 string='Free/Busy'),
1762 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: