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 if not isinstance(ids, list):
1001 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):
1003 if datas.get('interval', 0) < 0:
1004 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative'))
1005 if datas.get('count', 0) < 0:
1006 raise osv.except_osv(_('Warning!'), _('Count cannot be negative'))
1007 if datas['recurrency']:
1008 result[event] = self.compute_rule_string(datas)
1013 def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
1014 data = self._get_empty_rrule_data()
1016 data['recurrency'] = True
1017 for event in self.browse(cr, uid, ids, context=context):
1018 rdate = rule_date or event.date
1019 update_data = self._parse_rrule(field_value, dict(data), rdate)
1020 data.update(update_data)
1021 super(calendar_event, obj).write(cr, uid, ids, data, context=context)
1026 'id': fields.integer('ID', readonly=True),
1027 'sequence': fields.integer('Sequence'),
1028 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1029 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1030 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1031 'create_date': fields.datetime('Created', readonly=True),
1032 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1033 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1034 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1035 ('confidential', 'Public for Employees')], 'Mark as', states={'done': [('readonly', True)]}),
1036 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1037 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1038 'Show as', states={'done': [('readonly', True)]}),
1039 'base_calendar_url': fields.char('Caldav URL', size=264),
1040 'state': fields.selection([('tentative', 'Tentative'),
1041 ('confirmed', 'Confirmed'),
1042 ('cancelled', 'Cancelled')], 'State', readonly=True),
1043 'exdate': fields.text('Exception Date/Times', help="This property \
1044 defines the list of date/time exceptions for a recurring calendar component."),
1045 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1046 rule or repeating pattern of time to exclude from the recurring rule."),
1047 'rrule': fields.function(_get_rulestring, type='char', size=124, \
1048 fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
1049 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1050 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1051 ('yearly', 'Yearly'),],
1052 'Recurrency', states={'done': [('readonly', True)]},
1053 help="Let the event automatically repeat at that interval"),
1054 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1055 help="Set an alarm at this time, before the event occurs" ),
1056 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1057 'recurrent_uid': fields.integer('Recurrent ID'),
1058 'recurrent_id': fields.datetime('Recurrent ID date'),
1059 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1060 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1061 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1062 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1063 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence termination'),
1064 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1065 'count': fields.integer('Repeat', help="Repeat x times"),
1066 'mo': fields.boolean('Mon'),
1067 'tu': fields.boolean('Tue'),
1068 'we': fields.boolean('Wed'),
1069 'th': fields.boolean('Thu'),
1070 'fr': fields.boolean('Fri'),
1071 'sa': fields.boolean('Sat'),
1072 'su': fields.boolean('Sun'),
1073 'select1': fields.selection([('date', 'Date of month'),
1074 ('day', 'Day of month')], 'Option'),
1075 'day': fields.integer('Date of month'),
1076 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1077 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1078 ('FR', 'Friday'), ('SA', 'Saturday'), \
1079 ('SU', 'Sunday')], 'Weekday'),
1080 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1081 ('3', 'Third'), ('4', 'Fourth'), \
1082 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1083 'month_list': fields.selection(months.items(), 'Month'),
1084 'end_date': fields.date('Repeat Until'),
1085 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1086 'event_id', 'attendee_id', 'Attendees'),
1087 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1088 'active': fields.boolean('Active', help="If the active field is set to \
1089 true, it will allow you to hide the event alarm information without removing it."),
1090 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1093 def default_organizer(self, cr, uid, context=None):
1094 user_pool = self.pool.get('res.users')
1095 user = user_pool.browse(cr, uid, uid, context=context)
1098 res += " <%s>" %(user.user_email)
1102 'end_type' : 'count',
1104 'rrule_type' : 'none',
1105 'state': 'tentative',
1111 'user_id': lambda self, cr, uid, ctx: uid,
1112 'organizer': default_organizer,
1115 def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None):
1116 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1117 This method gives ids of dates that comes between start date and end date of calendar views
1118 @param self: The object pointer
1119 @param cr: the current row, from the database cursor,
1120 @param uid: the current user’s ID for security checks,
1121 @param limit: The Number of Results to Return """
1126 for data in super(calendar_event, self).read(cr, uid, select, context=context):
1127 if not data['rrule']:
1128 result.append(data['id'])
1130 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1131 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1133 if not data['rrule']:
1136 exdate = data['exdate'] and data['exdate'].split(',') or []
1137 rrule_str = data['rrule']
1139 rrule_until_date = False
1141 for rule in rrule_str.split(';'):
1142 name, value = rule.split('=')
1145 value = parser.parse(value)
1146 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1147 value = value.strftime("%Y%m%d%H%M%S")
1148 new_rule = '%s=%s' % (name, value)
1149 new_rrule_str.append(new_rule)
1150 new_rrule_str = ';'.join(new_rrule_str)
1151 rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
1152 for r_date in rdates:
1155 if arg[0] in ('date', 'date_deadline'):
1157 ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
1159 ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
1161 ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
1163 ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
1165 ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
1168 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1169 result.append(idval)
1171 if isinstance(select, (str, int, long)):
1172 return ids and ids[0] or False
1174 ids = list(set(result))
1177 def compute_rule_string(self, datas):
1179 Compute rule string according to value type RECUR of iCalendar from the values given.
1180 @param self: the object pointer
1181 @param datas: dictionary of freq and interval value.
1183 def get_week_string(freq, datas):
1184 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1185 if freq == 'weekly':
1186 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1188 return ';BYDAY=' + ','.join(byday)
1191 def get_month_string(freq, datas):
1192 if freq == 'monthly':
1193 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1194 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1196 if datas.get('select1')=='day':
1197 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1198 elif datas.get('select1')=='date':
1199 return ';BYMONTHDAY=' + str(datas.get('day'))
1202 def get_end_date(datas):
1203 if datas.get('end_date'):
1204 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1206 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1207 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1209 freq=datas.get('rrule_type')
1213 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1215 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1217 def _get_empty_rrule_data(self):
1220 'recurrency' : False,
1222 'rrule_type' : False,
1239 #def _write_rrule(self, cr, uid, ids, field_value, rule_date=False, context=None):
1240 # data = self._get_empty_rrule_data()
1243 # data['recurrency'] = True
1244 # for event in self.browse(cr, uid, ids, context=context):
1245 # rdate = rule_date or event.date
1246 # update_data = self._parse_rrule(field_value, dict(data), rdate)
1247 # data.update(update_data)
1249 # self.write(cr, uid, event.id, data, context=context)
1252 def _parse_rrule(self, rule, data, date_start):
1253 day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1254 rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1255 r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1257 if r._freq > 0 and r._freq < 4:
1258 data['rrule_type'] = rrule_type[r._freq]
1260 data['count'] = r._count
1261 data['interval'] = r._interval
1262 data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1265 for i in xrange(0,7):
1266 if i in r._byweekday:
1267 data[day_list[i]] = True
1268 data['rrule_type'] = 'weekly'
1269 #repeat monthly bynweekday ((weekday, weeknumber), )
1271 data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1272 data['byday'] = r._bynweekday[0][1]
1273 data['select1'] = 'day'
1274 data['rrule_type'] = 'monthly'
1277 data['day'] = r._bymonthday[0]
1278 data['select1'] = 'date'
1279 data['rrule_type'] = 'monthly'
1281 #yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1283 data['interval'] = data['interval'] * 12
1285 #FIXEME handle forever case
1287 #in case of repeat for ever that we do not support right now
1288 if not (data.get('count') or data.get('end_date')):
1290 if data.get('count'):
1291 data['end_type'] = 'count'
1293 data['end_type'] = 'end_date'
1296 def remove_virtual_id(self, ids):
1297 if isinstance(ids, (str, int, long)):
1298 return base_calendar_id2real_id(ids)
1300 if isinstance(ids, (list, tuple)):
1303 res.append(base_calendar_id2real_id(id))
1306 def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1307 context = context or {}
1308 args_without_date = []
1313 new_id = self.remove_virtual_id(arg[2])
1314 new_arg = (arg[0], arg[1], new_id)
1315 args_without_date.append(new_arg)
1316 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1317 args_without_date.append(arg)
1319 if context.get('virtual_id', True):
1320 args_without_date.append('|')
1321 args_without_date.append(arg)
1322 if context.get('virtual_id', True):
1323 args_without_date.append(('recurrency','=',1))
1324 filter_date.append(arg)
1326 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1327 0, 0, order, context, count=False)
1328 if context.get('virtual_id', True):
1329 res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
1334 return res[offset:offset+limit]
1338 def _get_data(self, cr, uid, id, context=None):
1339 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1342 def need_to_update(self, event_id, vals):
1343 split_id = str(event_id).split("-")
1344 if len(split_id) < 2:
1347 date_start = vals.get('date', '')
1349 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1350 return date_start == split_id[1]
1355 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1356 context = context or {}
1357 if isinstance(ids, (str, int, long)):
1361 # Special write of complex IDS
1362 for event_id in ids[:]:
1363 if len(str(event_id).split('-')) == 1:
1365 ids.remove(event_id)
1366 real_event_id = base_calendar_id2real_id(event_id)
1367 if not vals.get('recurrency', True):
1368 ids.append(real_event_id)
1371 #if edit one instance of a reccurrent id
1372 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1373 'rrule', 'duration', 'exdate'])
1374 if data.get('rrule'):
1377 'recurrent_uid': real_event_id,
1378 'recurrent_id': data.get('date'),
1379 'rrule_type': 'none',
1381 'recurrency' : False,
1384 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1386 date_new = event_id.split('-')[1]
1387 date_new = time.strftime("%Y%m%dT%H%M%S", \
1388 time.strptime(date_new, "%Y%m%d%H%M%S"))
1389 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1390 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1392 context.update({'active_id': new_id, 'active_ids': [new_id]})
1395 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1396 vals['vtimezone'] = vals['vtimezone'][40:]
1398 res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
1400 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1401 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1402 alarm_obj = self.pool.get('res.alarm')
1403 alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context)
1404 return res or True and False
1406 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1410 if 'date' in groupby:
1411 raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1412 virtual_id = context.get('virtual_id', True)
1413 context.update({'virtual_id': False})
1414 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1416 #remove the count, since the value is not consistent with the result of the search when expand the group
1417 for groupname in groupby:
1418 if re.get(groupname + "_count"):
1419 del re[groupname + "_count"]
1420 re.get('__context', {}).update({'virtual_id' : virtual_id})
1423 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1424 # FIXME This whole id mangling has to go!
1427 fields2 = fields and fields[:] or None
1429 EXTRAFIELDS = ('class','user_id','duration')
1430 for f in EXTRAFIELDS:
1431 if fields and (f not in fields):
1434 if isinstance(ids, (str, int, long)):
1438 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1441 real_data = super(calendar_event, self).read(cr, uid,
1442 [real_id for base_calendar_id, real_id in select],
1443 fields=fields2, context=context, load=load)
1444 real_data = dict(zip([x['id'] for x in real_data], real_data))
1446 for base_calendar_id, real_id in select:
1447 res = real_data[real_id].copy()
1448 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1449 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1451 res['date_deadline'] = ls[2]
1452 res['id'] = base_calendar_id
1458 user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id']
1461 if r['class']=='private':
1463 if f not in ('id','date','date_deadline','duration','user_id','state'):
1469 for k in EXTRAFIELDS:
1470 if (k in r) and ((not fields) or (k not in fields)):
1472 if isinstance(ids, (str, int, long)):
1473 return result and result[0] or False
1476 def copy(self, cr, uid, id, default=None, context=None):
1480 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1481 alarm_obj = self.pool.get('res.alarm')
1482 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1485 def unlink(self, cr, uid, ids, context=None):
1486 if not isinstance(ids, list):
1489 attendee_obj=self.pool.get('calendar.attendee')
1490 for event_id in ids[:]:
1491 if len(str(event_id).split('-')) == 1:
1494 real_event_id = base_calendar_id2real_id(event_id)
1495 data = self.read(cr, uid, real_event_id, ['exdate'], context=context)
1496 date_new = event_id.split('-')[1]
1497 date_new = time.strftime("%Y%m%dT%H%M%S", \
1498 time.strptime(date_new, "%Y%m%d%H%M%S"))
1499 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1500 self.write(cr, uid, [real_event_id], {'exdate': exdate})
1501 ids.remove(event_id)
1502 for event in self.browse(cr, uid, ids, context=context):
1503 if event.attendee_ids:
1504 attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context)
1506 res = super(calendar_event, self).unlink(cr, uid, ids, context=context)
1507 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name)
1508 self.unlink_events(cr, uid, ids, context=context)
1512 def create(self, cr, uid, vals, context=None):
1516 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1517 vals['vtimezone'] = vals['vtimezone'][40:]
1519 #updated_vals = self.onchange_dates(cr, uid, [],
1520 # vals.get('date', False),
1521 # vals.get('duration', False),
1522 # vals.get('date_deadline', False),
1523 # vals.get('allday', False),
1525 #vals.update(updated_vals.get('value', {}))
1527 res = super(calendar_event, self).create(cr, uid, vals, context)
1528 alarm_obj = self.pool.get('res.alarm')
1529 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1532 def do_tentative(self, cr, uid, ids, context=None, *args):
1533 """ Makes event invitation as Tentative
1534 @param self: The object pointer
1535 @param cr: the current row, from the database cursor,
1536 @param uid: the current user’s ID for security checks,
1537 @param ids: List of Event IDs
1538 @param *args: Get Tupple value
1539 @param context: A standard dictionary for contextual values
1541 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1543 def do_cancel(self, cr, uid, ids, context=None, *args):
1544 """ Makes event invitation as Tentative
1545 @param self: The object pointer
1546 @param cr: the current row, from the database cursor,
1547 @param uid: the current user’s ID for security checks,
1548 @param ids: List of Event IDs
1549 @param *args: Get Tupple value
1550 @param context: A standard dictionary for contextual values
1552 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1554 def do_confirm(self, cr, uid, ids, context=None, *args):
1555 """ Makes event invitation as Tentative
1556 @param self: The object pointer
1557 @param cr: the current row, from the database cursor,
1558 @param uid: the current user’s ID for security checks,
1559 @param ids: List of Event IDs
1560 @param *args: Get Tupple value
1561 @param context: A standard dictionary for contextual values
1563 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1567 class calendar_todo(osv.osv):
1568 """ Calendar Task """
1570 _name = "calendar.todo"
1571 _inherit = "calendar.event"
1572 _description = "Calendar Task"
1574 def _get_date(self, cr, uid, ids, name, arg, context=None):
1577 @param self: The object pointer
1578 @param cr: the current row, from the database cursor,
1579 @param uid: the current user’s ID for security checks,
1580 @param ids: List of calendar todo's IDs.
1581 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1582 @param context: A standard dictionary for contextual values
1586 for event in self.browse(cr, uid, ids, context=context):
1587 res[event.id] = event.date_start
1590 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1593 @param self: The object pointer
1594 @param cr: the current row, from the database cursor,
1595 @param uid: the current user’s ID for security checks,
1596 @param id: calendar's ID.
1597 @param value: Get Value
1598 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1599 @param context: A standard dictionary for contextual values
1602 assert name == 'date'
1603 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1606 'date': fields.function(_get_date, fnct_inv=_set_date, \
1607 string='Duration', store=True, type='datetime'),
1608 'duration': fields.integer('Duration'),
1616 class ir_attachment(osv.osv):
1617 _name = 'ir.attachment'
1618 _inherit = 'ir.attachment'
1620 def search_count(self, cr, user, args, context=None):
1622 @param self: The object pointer
1623 @param cr: the current row, from the database cursor,
1624 @param user: the current user’s ID for security checks,
1625 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1626 @param context: A standard dictionary for contextual values
1631 args1.append(map(lambda x:str(x).split('-')[0], arg))
1632 return super(ir_attachment, self).search_count(cr, user, args1, context)
1636 def create(self, cr, uid, vals, context=None):
1638 id = context.get('default_res_id', False)
1639 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1640 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1642 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1643 context=None, count=False):
1645 @param self: The object pointer
1646 @param cr: the current row, from the database cursor,
1647 @param uid: the current user’s ID for security checks,
1648 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1649 @param offset: The Number of Results to pass,
1650 @param limit: The Number of Results to Return,
1651 @param context: A standard dictionary for contextual values
1655 for i, arg in enumerate(new_args):
1656 if arg[0] == 'res_id':
1657 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1659 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1660 limit=limit, order=order, context=context, count=False)
1663 class ir_values(osv.osv):
1664 _inherit = 'ir.values'
1666 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1667 isobject=False, meta=False, preserve_user=False, company=False):
1670 @param self: The object pointer
1671 @param cr: the current row, from the database cursor,
1672 @param uid: the current user’s ID for security checks,
1673 @param model: Get The Model
1678 if type(data) in (list, tuple):
1679 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1681 new_model.append(data)
1682 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1683 value, replace, isobject, meta, preserve_user, company)
1685 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1686 res_id_req=False, without_user=True, key2_req=True):
1689 @param self: The object pointer
1690 @param cr: the current row, from the database cursor,
1691 @param uid: the current user’s ID for security checks,
1692 @param model: Get The Model
1698 if type(data) in (list, tuple):
1699 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1701 new_model.append(data)
1702 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1703 meta, context, res_id_req, without_user, key2_req)
1707 class ir_model(osv.osv):
1709 _inherit = 'ir.model'
1711 def read(self, cr, uid, ids, fields=None, context=None,
1712 load='_classic_read'):
1714 Overrides orm read method.
1715 @param self: The object pointer
1716 @param cr: the current row, from the database cursor,
1717 @param uid: the current user’s ID for security checks,
1718 @param ids: List of IR Model’s IDs.
1719 @param context: A standard dictionary for contextual values
1721 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1724 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1725 context=context, load=load)
1728 val['id'] = base_calendar_id2real_id(val['id'])
1729 return isinstance(ids, (str, int, long)) and data[0] or data
1733 class virtual_report_spool(web_services.report_spool):
1735 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1738 @param self: The object pointer
1739 @param db: get the current database,
1740 @param uid: the current user’s ID for security checks,
1741 @param context: A standard dictionary for contextual values
1744 if object == 'printscreen.list':
1745 return super(virtual_report_spool, self).exp_report(db, uid, \
1746 object, ids, datas, context)
1749 new_ids.append(base_calendar_id2real_id(id))
1750 if datas.get('id', False):
1751 datas['id'] = base_calendar_id2real_id(datas['id'])
1752 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1754 virtual_report_spool()
1756 class res_users(osv.osv):
1757 _inherit = 'res.users'
1759 def _get_user_avail(self, cr, uid, ids, context=None):
1761 Get User Availability
1762 @param self: The object pointer
1763 @param cr: the current row, from the database cursor,
1764 @param uid: the current user’s ID for security checks,
1765 @param ids: List of res user’s IDs.
1766 @param context: A standard dictionary for contextual values
1769 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1771 attendee_obj = self.pool.get('calendar.attendee')
1772 attendee_ids = attendee_obj.search(cr, uid, [
1773 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1774 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1777 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1778 user_id = attendee_data['user_id']
1780 res.update({user_id:status})
1782 #TOCHECK: Delegated Event
1784 if user_id not in res:
1785 res[user_id] = 'free'
1789 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1791 Get User Availability Function
1792 @param self: The object pointer
1793 @param cr: the current row, from the database cursor,
1794 @param uid: the current user’s ID for security checks,
1795 @param ids: List of res user’s IDs.
1796 @param context: A standard dictionary for contextual values
1799 return self._get_user_avail(cr, uid, ids, context=context)
1802 'availability': fields.function(_get_user_avail_fun, type='selection', \
1803 selection=[('free', 'Free'), ('busy', 'Busy')], \
1804 string='Free/Busy'),
1810 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: