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.address_id.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, method=True, \
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, method=True, 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, method=True, 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, method=True, string='Sent By User', \
358 type="many2one", relation="res.users", multi='sent_by_uid'),
359 'cn': fields.function(_compute_data, method=True, 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, method=True, 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, method=True, string='Event Date', \
373 type="datetime", multi='event_date'),
374 'event_end_date': fields.function(_compute_data, method=True, \
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!'), _('Can not Duplicate'))
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 for att in self.browse(cr, uid, ids, context=context):
488 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
489 sign = '<br>'.join(sign and sign.split('\n') or [])
494 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
496 for att2 in self.browse(cr, uid, other_invitation_ids):
497 att_infos.append(((att2.user_id and att2.user_id.name) or \
498 (att2.partner_id and att2.partner_id.name) or \
499 att2.email) + ' - Status: ' + att2.state.title())
500 body_vals = {'name': res_obj.name,
501 'start_date': res_obj.date,
502 'end_date': res_obj.date_deadline or False,
503 'description': res_obj.description or '-',
504 'location': res_obj.location or '-',
505 'attendees': '<br>'.join(att_infos),
506 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
510 body = html_invitation % body_vals
511 if mail_to and email_from:
512 attach = self.get_ics_file(cr, uid, res_obj, context=context)
518 attach=attach and [('invitation.ics', attach)] or None,
524 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
526 Make entry on email and availbility on change of user_id field.
527 @param cr: the current row, from the database cursor,
528 @param uid: the current user’s ID for security checks,
529 @param ids: List of calendar attendee’s IDs.
530 @param user_id: Changed value of User id
531 @return: dictionary of value. which put value in email and availability fields.
535 return {'value': {'email': ''}}
536 usr_obj = self.pool.get('res.users')
537 user = usr_obj.browse(cr, uid, user_id, *args)
538 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
540 def do_tentative(self, cr, uid, ids, context=None, *args):
541 """ Makes event invitation as Tentative
542 @param self: The object pointer
543 @param cr: the current row, from the database cursor,
544 @param uid: the current user’s ID for security checks,
545 @param ids: List of calendar attendee’s IDs
546 @param *args: Get Tupple value
547 @param context: A standard dictionary for contextual values
549 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
551 def do_accept(self, cr, uid, ids, context=None, *args):
553 Update state of invitation as Accepted and
554 if the invited user is other then event user it will make a copy of this event for invited user
555 @param cr: the current row, from the database cursor,
556 @param uid: the current user’s ID for security checks,
557 @param ids: List of calendar attendee’s IDs.
558 @param context: A standard dictionary for contextual values
564 for vals in self.browse(cr, uid, ids, context=context):
565 if vals.ref and vals.ref.user_id:
566 mod_obj = self.pool.get(vals.ref._name)
567 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
568 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
569 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
573 def do_decline(self, cr, uid, ids, context=None, *args):
574 """ Marks event invitation as Declined
575 @param self: The object pointer
576 @param cr: the current row, from the database cursor,
577 @param uid: the current user’s ID for security checks,
578 @param ids: List of calendar attendee’s IDs
579 @param *args: Get Tupple value
580 @param context: A standard dictionary for contextual values """
583 return self.write(cr, uid, ids, {'state': 'declined'}, context)
585 def create(self, cr, uid, vals, context=None):
586 """ Overrides orm create method.
587 @param self: The object pointer
588 @param cr: the current row, from the database cursor,
589 @param uid: the current user’s ID for security checks,
590 @param vals: Get Values
591 @param context: A standard dictionary for contextual values """
595 if not vals.get("email") and vals.get("cn"):
596 cnval = vals.get("cn").split(':')
597 email = filter(lambda x:x.__contains__('@'), cnval)
598 vals['email'] = email and email[0] or ''
599 vals['cn'] = vals.get("cn")
600 res = super(calendar_attendee, self).create(cr, uid, vals, context)
604 class res_alarm(osv.osv):
605 """Resource Alarm """
607 _description = 'Basic Alarm Information'
610 'name':fields.char('Name', size=256, required=True),
611 'trigger_occurs': fields.selection([('before', 'Before'), \
612 ('after', 'After')], \
613 'Triggers', required=True),
614 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
615 ('hours', 'Hours'), \
616 ('days', 'Days')], 'Interval', \
618 'trigger_duration': fields.integer('Duration', required=True),
619 'trigger_related': fields.selection([('start', 'The event starts'), \
620 ('end', 'The event ends')], \
621 'Related to', required=True),
622 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
623 are both optional, but if one occurs, so MUST the other"""),
624 'repeat': fields.integer('Repeat'),
625 'active': fields.boolean('Active', help="If the active field is set to \
626 true, it will allow you to hide the event alarm information without removing it.")
629 'trigger_interval': 'minutes',
630 'trigger_duration': 5,
631 'trigger_occurs': 'before',
632 'trigger_related': 'start',
636 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
638 Create Alarm for event.
639 @param cr: the current row, from the database cursor,
640 @param uid: the current user’s ID for security checks,
641 @param ids: List of res alarm’s IDs.
642 @param model: Model name.
643 @param date: Event date
644 @param context: A standard dictionary for contextual values
649 alarm_obj = self.pool.get('calendar.alarm')
650 res_alarm_obj = self.pool.get('res.alarm')
651 ir_obj = self.pool.get('ir.model')
652 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
654 model_obj = self.pool.get(model)
655 for data in model_obj.browse(cr, uid, ids, context=context):
657 basic_alarm = data.alarm_id
658 cal_alarm = data.base_calendar_alarm_id
659 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
661 # Find for existing res.alarm
662 duration = cal_alarm.trigger_duration
663 interval = cal_alarm.trigger_interval
664 occurs = cal_alarm.trigger_occurs
665 related = cal_alarm.trigger_related
666 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
667 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
670 'trigger_duration': duration,
671 'trigger_interval': interval,
672 'trigger_occurs': occurs,
673 'trigger_related': related,
674 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
676 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
678 new_res_alarm = alarm_ids[0]
679 cr.execute('UPDATE %s ' % model_obj._table + \
680 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
682 (cal_alarm.id, new_res_alarm, data.id))
684 self.do_alarm_unlink(cr, uid, [data.id], model)
688 'description': data.description,
690 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
691 'trigger_related': basic_alarm.trigger_related,
692 'trigger_duration': basic_alarm.trigger_duration,
693 'trigger_occurs': basic_alarm.trigger_occurs,
694 'trigger_interval': basic_alarm.trigger_interval,
695 'duration': basic_alarm.duration,
696 'repeat': basic_alarm.repeat,
698 'event_date': data[date],
700 'model_id': model_id,
703 alarm_id = alarm_obj.create(cr, uid, vals)
704 cr.execute('UPDATE %s ' % model_obj._table + \
705 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
707 ( alarm_id, basic_alarm.id, data.id) )
710 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
712 Delete alarm specified in ids
713 @param cr: the current row, from the database cursor,
714 @param uid: the current user’s ID for security checks,
715 @param ids: List of res alarm’s IDs.
716 @param model: Model name for which alarm is to be cleared.
721 alarm_obj = self.pool.get('calendar.alarm')
722 ir_obj = self.pool.get('ir.model')
723 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
724 model_obj = self.pool.get(model)
725 for datas in model_obj.browse(cr, uid, ids, context=context):
726 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
728 alarm_obj.unlink(cr, uid, alarm_ids)
729 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
730 where id=%%s' % model_obj._table,(datas.id,))
735 class calendar_alarm(osv.osv):
736 _name = 'calendar.alarm'
737 _description = 'Event alarm information'
738 _inherit = 'res.alarm'
742 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
743 'name': fields.char('Summary', size=124, help="""Contains the text to be \
744 used as the message subject for email \
745 or contains the text to be used for display"""),
746 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
747 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
748 required=True, help="Defines the action to be invoked when an alarm is triggered"),
749 'description': fields.text('Description', help='Provides a more complete \
750 description of the calendar component, than that \
751 provided by the "SUMMARY" property'),
752 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
753 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
754 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
755 which is rendered when the alarm is triggered for audio,
756 * File which is intended to be sent as message attachments for email,
757 * Points to a procedure resource, which is invoked when\
758 the alarm is triggered for procedure."""),
759 'res_id': fields.integer('Resource ID'),
760 'model_id': fields.many2one('ir.model', 'Model'),
761 'user_id': fields.many2one('res.users', 'Owner'),
762 'event_date': fields.datetime('Event Date'),
763 'event_end_date': fields.datetime('Event End Date'),
764 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
765 'state':fields.selection([
770 ], 'State', select=True, readonly=True),
778 def create(self, cr, uid, vals, context=None):
780 Overrides orm create method.
781 @param self: The object pointer
782 @param cr: the current row, from the database cursor,
783 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
784 @param context: A standard dictionary for contextual values
785 @return: new record id for calendar_alarm.
789 event_date = vals.get('event_date', False)
791 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
792 if vals['trigger_interval'] == 'days':
793 delta = timedelta(days=vals['trigger_duration'])
794 if vals['trigger_interval'] == 'hours':
795 delta = timedelta(hours=vals['trigger_duration'])
796 if vals['trigger_interval'] == 'minutes':
797 delta = timedelta(minutes=vals['trigger_duration'])
798 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
799 vals['trigger_date'] = trigger_date
800 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
803 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
805 """Scheduler for event reminder
806 @param self: The object pointer
807 @param cr: the current row, from the database cursor,
808 @param uid: the current user’s ID for security checks,
809 @param ids: List of calendar alarm’s IDs.
810 @param use_new_cursor: False or the dbname
811 @param context: A standard dictionary for contextual values
813 return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
816 current_datetime = datetime.now()
817 request_obj = self.pool.get('res.request')
818 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
822 for alarm in self.browse(cr, uid, alarm_ids, context=context):
823 next_trigger_date = None
825 model_obj = self.pool.get(alarm.model_id.model)
826 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
830 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
831 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
833 trigger_interval = alarm.trigger_interval
834 if trigger_interval == 'days':
835 delta = timedelta(days=alarm.trigger_duration)
836 if trigger_interval == 'hours':
837 delta = timedelta(hours=alarm.trigger_duration)
838 if trigger_interval == 'minutes':
839 delta = timedelta(minutes=alarm.trigger_duration)
840 delta = alarm.trigger_occurs == 'after' and delta or -delta
842 for rdate in recurrent_dates:
843 if rdate + delta > current_datetime:
845 if rdate + delta <= current_datetime:
846 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
847 rest_dates = recurrent_dates[len(re_dates):]
848 next_trigger_date = rest_dates and rest_dates[0] or None
851 re_dates = [alarm.trigger_date]
853 for r_date in re_dates:
854 ref = alarm.model_id.model + ',' + str(alarm.res_id)
856 # search for alreay sent requests
857 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
860 if alarm.action == 'display':
863 'act_from': alarm.user_id.id,
864 'act_to': alarm.user_id.id,
865 'body': alarm.description,
866 'trigger_date': r_date,
869 request_id = request_obj.create(cr, uid, value)
870 request_ids = [request_id]
871 for attendee in res_obj.attendee_ids:
873 value['act_to'] = attendee.user_id.id
874 request_id = request_obj.create(cr, uid, value)
875 request_ids.append(request_id)
876 request_obj.request_send(cr, uid, request_ids)
878 if alarm.action == 'email':
879 sub = '[Openobject Reminder] %s' % (alarm.name)
891 """ % (alarm.name, alarm.trigger_date, alarm.description, \
892 alarm.user_id.name, alarm.user_id.signature)
893 mail_to = [alarm.user_id.address_id.email]
894 for att in alarm.attendee_ids:
895 mail_to.append(att.user_id.address_id.email)
898 tools.config.get('email_from', False),
903 if next_trigger_date:
904 update_vals.update({'trigger_date': next_trigger_date})
906 update_vals.update({'state': 'done'})
907 self.write(cr, uid, [alarm.id], update_vals)
913 class calendar_event(osv.osv):
914 _name = "calendar.event"
915 _description = "Calendar Event"
918 def _tz_get(self, cr, uid, context=None):
919 return [(x.lower(), x) for x in pytz.all_timezones]
921 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
922 """Returns duration and/or end date based on values passed
923 @param self: The object pointer
924 @param cr: the current row, from the database cursor,
925 @param uid: the current user’s ID for security checks,
926 @param ids: List of calendar event’s IDs.
927 @param start_date: Starting date
928 @param duration: Duration between start date and end date
929 @param end_date: Ending Datee
930 @param context: A standard dictionary for contextual values
938 if not end_date and not duration:
940 value['duration'] = duration
942 if allday: # For all day event
943 value = {'duration': 24.0}
946 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
947 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
948 value['date'] = start_date
951 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
952 if end_date and not duration:
953 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
955 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
956 value['duration'] = round(duration, 2)
958 end = start + timedelta(hours=duration)
959 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
960 elif end_date and duration and not allday:
961 # we have both, keep them synchronized:
962 # set duration based on end_date (arbitrary decision: this avoid
963 # getting dates like 06:31:48 instead of 06:32:00)
964 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
966 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
967 value['duration'] = round(duration, 2)
969 return {'value': value}
971 def unlink_events(self, cr, uid, ids, context=None):
973 This function deletes event which are linked with the event with recurrent_uid
974 (Removes the events which refers to the same UID value)
979 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
980 r_ids = map(lambda x: x[0], cr.fetchall())
981 self.unlink(cr, uid, r_ids, context=context)
984 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
986 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
987 @param self: The object pointer
988 @param cr: the current row, from the database cursor,
989 @param id: List of calendar event's ids.
990 @param context: A standard dictionary for contextual values
991 @return: dictionary of rrule value.
995 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):
997 if datas.get('interval', 0) < 0:
998 raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
999 if datas.get('count', 0) < 0:
1000 raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1001 result[event] = self.compute_rule_string(datas)
1005 'id': fields.integer('ID'),
1006 'sequence': fields.integer('Sequence'),
1007 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1008 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1009 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1010 'create_date': fields.datetime('Created', readonly=True),
1011 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1012 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1013 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1014 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1015 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1016 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1017 'Show as', states={'done': [('readonly', True)]}),
1018 'base_calendar_url': fields.char('Caldav URL', size=264),
1019 'state': fields.selection([('tentative', 'Tentative'),
1020 ('confirmed', 'Confirmed'),
1021 ('cancelled', 'Cancelled')], 'State', readonly=True),
1022 'exdate': fields.text('Exception Date/Times', help="This property \
1023 defines the list of date/time exceptions for a recurring calendar component."),
1024 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1025 rule or repeating pattern of time to exclude from the recurring rule."),
1026 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1027 string='Recurrent Rule'),
1028 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1029 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1030 ('yearly', 'Yearly'),],
1031 'Recurrency', states={'done': [('readonly', True)]},
1032 help="Let the event automatically repeat at that interval"),
1033 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1034 help="Set an alarm at this time, before the event occurs" ),
1035 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1036 'recurrent_uid': fields.integer('Recurrent ID'),
1037 'recurrent_id': fields.datetime('Recurrent ID date'),
1038 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1039 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1040 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1041 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1042 'end_type' : fields.selection([('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1043 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1044 'count': fields.integer('Repeat', help="Repeat x times"),
1045 'mo': fields.boolean('Mon'),
1046 'tu': fields.boolean('Tue'),
1047 'we': fields.boolean('Wed'),
1048 'th': fields.boolean('Thu'),
1049 'fr': fields.boolean('Fri'),
1050 'sa': fields.boolean('Sat'),
1051 'su': fields.boolean('Sun'),
1052 'select1': fields.selection([('date', 'Date of month'),
1053 ('day', 'Day of month')], 'Option'),
1054 'day': fields.integer('Date of month'),
1055 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1056 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1057 ('FR', 'Friday'), ('SA', 'Saturday'), \
1058 ('SU', 'Sunday')], 'Weekday'),
1059 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1060 ('3', 'Third'), ('4', 'Fourth'), \
1061 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1062 'month_list': fields.selection(months.items(), 'Month'),
1063 'end_date': fields.date('Repeat Until'),
1064 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1065 'event_id', 'attendee_id', 'Attendees'),
1066 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1067 'active': fields.boolean('Active', help="If the active field is set to \
1068 true, it will allow you to hide the event alarm information without removing it."),
1069 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1070 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1072 def default_organizer(self, cr, uid, context=None):
1073 user_pool = self.pool.get('res.users')
1074 user = user_pool.browse(cr, uid, uid, context=context)
1077 res += " <%s>" %(user.user_email)
1081 'end_type' : 'count',
1083 'rrule_type' : 'none',
1084 'state': 'tentative',
1090 'user_id': lambda self, cr, uid, ctx: uid,
1091 'organizer': default_organizer,
1095 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
1096 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1097 This method gives ids of dates that comes between start date and end date of calendar views
1098 @param self: The object pointer
1099 @param cr: the current row, from the database cursor,
1100 @param uid: the current user’s ID for security checks,
1101 @param base_start_date: Get Start Date
1102 @param base_until_date: Get End Date
1103 @param limit: The Number of Results to Return """
1107 virtual_id = context and context.get('virtual_id', False) or False
1109 if isinstance(select, (str, int, long)):
1115 if ids and virtual_id:
1116 for data in super(calendar_event, self).read(cr, uid, ids, context=context):
1117 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1118 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1119 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1120 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1122 if not data['rrule']:
1123 if start_date and (event_date < start_date):
1125 if until_date and (event_date > until_date):
1128 result.append(idval)
1130 start_date = event_date
1131 exdate = data['exdate'] and data['exdate'].split(',') or []
1132 rrule_str = data['rrule']
1134 rrule_until_date = False
1136 for rule in rrule_str.split(';'):
1137 name, value = rule.split('=')
1140 value = parser.parse(value)
1141 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
1142 if until_date and until_date >= rrule_until_date:
1143 until_date = rrule_until_date
1145 value = until_date.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 if not is_until and until_date:
1151 value = until_date.strftime("%Y%m%d%H%M%S")
1153 new_rule = '%s=%s' % (name, value)
1154 new_rrule_str.append(new_rule)
1155 new_rrule_str = ';'.join(new_rrule_str)
1156 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1158 for r_date in rdates:
1159 if start_date and r_date < start_date:
1161 if until_date and r_date > until_date:
1163 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1164 result.append(idval)
1167 ids = list(set(result))
1168 if isinstance(select, (str, int, long)):
1169 return ids and ids[0] or False
1172 def compute_rule_string(self, datas):
1174 Compute rule string according to value type RECUR of iCalendar from the values given.
1175 @param self: the object pointer
1176 @param datas: dictionary of freq and interval value.
1179 def get_week_string(freq, datas):
1180 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1181 if freq == 'weekly':
1182 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1184 return ';BYDAY=' + ','.join(byday)
1187 def get_month_string(freq, datas):
1188 if freq == 'monthly':
1189 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1190 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1192 if datas.get('select1')=='day':
1193 return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1194 elif datas.get('select1')=='date':
1195 return ';BYMONTHDAY=' + str(datas.get('day'))
1198 def get_end_date(datas):
1199 if datas.get('end_date'):
1200 datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1202 return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
1203 ((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
1205 freq=datas.get('rrule_type')
1208 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1209 return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
1212 def remove_virtual_id(self, ids):
1213 if isinstance(ids, (str, int)):
1214 return base_calendar_id2real_id(ids)
1216 if isinstance(ids, (list, tuple)):
1219 res.append(base_calendar_id2real_id(id))
1222 def search(self, cr, uid, args, offset=0, limit=0, order=None,
1223 context=None, count=False):
1224 args_without_date = []
1230 new_id = self.remove_virtual_id(arg[2])
1231 new_arg = (arg[0], arg[1], new_id)
1232 args_without_date.append(new_arg)
1233 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1234 args_without_date.append(arg)
1236 if arg[1] in ('>', '>='):
1240 elif arg[1] in ('<', '<='):
1245 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1246 0, 0, order, context, count=False)
1247 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
1251 return res[offset:offset+limit]
1256 def get_edit_all(self, cr, uid, id, vals=None):
1258 return true if we have to edit all meeting from the same recurrent
1259 or only on occurency
1261 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1262 if(vals and 'edit_all' in vals): #we jsut check edit_all
1263 return vals['edit_all']
1264 else: #it's a recurrent event and edit_all is already check
1265 return meeting['recurrency'] and meeting['edit_all']
1267 def _get_data(self, cr, uid, id, context=None):
1268 res = self.read(cr, uid, [id],['date', 'date_deadline'])
1271 def need_to_update(self, event_id, vals):
1272 split_id = str(event_id).split("-")
1273 if len(split_id) < 2:
1276 date_start = vals.get('date', '')
1278 date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
1279 return date_start == split_id[1]
1283 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1286 if isinstance(ids, (str, int, long)):
1292 for event_id in select:
1293 real_event_id = base_calendar_id2real_id(event_id)
1295 edit_all = self.get_edit_all(cr, uid, event_id, vals=vals)
1297 if self.need_to_update(event_id, vals):
1298 res = self._get_data(cr, uid, real_event_id, context=context)
1300 event_id = real_event_id
1302 #if edit one instance of a reccurrent id
1303 if len(str(event_id).split('-')) > 1 and not edit_all:
1304 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1305 'rrule', 'duration', 'exdate'])
1306 if data.get('rrule'):
1309 'recurrent_uid': real_event_id,
1310 'recurrent_id': data.get('date'),
1311 'rrule_type': 'none',
1314 'recurrency' : False,
1317 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1319 date_new = event_id.split('-')[1]
1320 date_new = time.strftime("%Y%m%dT%H%M%S", \
1321 time.strptime(date_new, "%Y%m%d%H%M%S"))
1322 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1323 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1325 context.update({'active_id': new_id, 'active_ids': [new_id]})
1327 if not real_event_id in new_ids:
1328 new_ids.append(real_event_id)
1330 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1331 vals['vtimezone'] = vals['vtimezone'][40:]
1333 updated_vals = self.onchange_dates(cr, uid, new_ids,
1334 vals.get('date', False),
1335 vals.get('duration', False),
1336 vals.get('date_deadline', False),
1337 vals.get('allday', False),
1339 vals.update(updated_vals.get('value', {}))
1341 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1343 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1344 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1345 # change alarm details
1346 alarm_obj = self.pool.get('res.alarm')
1347 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1348 return res or True and False
1350 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1351 if isinstance(ids, (str, int, long)):
1355 select = map(lambda x: base_calendar_id2real_id(x), select)
1356 res = super(calendar_event, self).browse(cr, uid, select, context, \
1357 list_class, fields_process)
1358 if isinstance(ids, (str, int, long)):
1359 return res and res[0] or False
1363 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1367 if 'date' in groupby:
1368 raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1369 virtual_id = context.get('virtual_id', False)
1370 context.update({'virtual_id': False})
1371 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1373 #remove the count, since the value is not consistent with the result of the search when expand the group
1374 for groupname in groupby:
1375 if re.get(groupname + "_count"):
1376 del re[groupname + "_count"]
1377 re.get('__context').update({'virtual_id' : virtual_id})
1380 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1381 # FIXME This whole id mangling has to go!
1387 if isinstance(ids, (str, int, long)):
1391 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1393 if fields and 'date' not in fields:
1394 fields.append('date')
1395 if fields and 'duration' not in fields:
1396 fields.append('duration')
1399 for base_calendar_id, real_id in select:
1400 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1401 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1405 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1406 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1408 res['date_deadline'] = ls[2]
1409 res['id'] = base_calendar_id
1412 if isinstance(ids, (str, int, long)):
1413 return result and result[0] or False
1417 def copy(self, cr, uid, id, default=None, context=None):
1421 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1422 alarm_obj = self.pool.get('res.alarm')
1423 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1427 def unlink(self, cr, uid, ids, context=None):
1428 if not isinstance(ids, list):
1433 data_list = self.read(cr, uid, [id], ['date', 'rrule', 'exdate'], context=context)
1434 if len(data_list) < 1:
1436 event_data = data_list[0]
1437 event_id = event_data['id']
1439 if self.get_edit_all(cr, uid, event_id, vals=None):
1440 event_id = base_calendar_id2real_id(event_id)
1442 if isinstance(event_id, (int, long)):
1443 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1444 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1445 self.unlink_events(cr, uid, [event_id], context=context)
1447 str_event, date_new = event_id.split('-')
1448 event_id = int(str_event)
1449 if event_data['rrule']:
1450 # Remove one of the recurrent event
1451 date_new = time.strftime("%Y%m%dT%H%M%S", \
1452 time.strptime(date_new, "%Y%m%d%H%M%S"))
1453 exdate = (event_data['exdate'] and (event_data['exdate'] + ',') or '') + date_new
1454 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1456 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1457 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1458 self.unlink_events(cr, uid, [event_id], context=context)
1461 def create(self, cr, uid, vals, context=None):
1465 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1466 vals['vtimezone'] = vals['vtimezone'][40:]
1468 updated_vals = self.onchange_dates(cr, uid, [],
1469 vals.get('date', False),
1470 vals.get('duration', False),
1471 vals.get('date_deadline', False),
1472 vals.get('allday', False),
1474 vals.update(updated_vals.get('value', {}))
1475 res = super(calendar_event, self).create(cr, uid, vals, context)
1476 alarm_obj = self.pool.get('res.alarm')
1477 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1483 def do_tentative(self, cr, uid, ids, context=None, *args):
1484 """ Makes event invitation as Tentative
1485 @param self: The object pointer
1486 @param cr: the current row, from the database cursor,
1487 @param uid: the current user’s ID for security checks,
1488 @param ids: List of Event IDs
1489 @param *args: Get Tupple value
1490 @param context: A standard dictionary for contextual values
1492 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1494 def do_cancel(self, cr, uid, ids, context=None, *args):
1495 """ Makes event invitation as Tentative
1496 @param self: The object pointer
1497 @param cr: the current row, from the database cursor,
1498 @param uid: the current user’s ID for security checks,
1499 @param ids: List of Event IDs
1500 @param *args: Get Tupple value
1501 @param context: A standard dictionary for contextual values
1503 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1505 def do_confirm(self, cr, uid, ids, context=None, *args):
1506 """ Makes event invitation as Tentative
1507 @param self: The object pointer
1508 @param cr: the current row, from the database cursor,
1509 @param uid: the current user’s ID for security checks,
1510 @param ids: List of Event IDs
1511 @param *args: Get Tupple value
1512 @param context: A standard dictionary for contextual values
1514 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1518 class calendar_todo(osv.osv):
1519 """ Calendar Task """
1521 _name = "calendar.todo"
1522 _inherit = "calendar.event"
1523 _description = "Calendar Task"
1525 def _get_date(self, cr, uid, ids, name, arg, context=None):
1528 @param self: The object pointer
1529 @param cr: the current row, from the database cursor,
1530 @param uid: the current user’s ID for security checks,
1531 @param ids: List of calendar todo's IDs.
1532 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1533 @param context: A standard dictionary for contextual values
1537 for event in self.browse(cr, uid, ids, context=context):
1538 res[event.id] = event.date_start
1541 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1544 @param self: The object pointer
1545 @param cr: the current row, from the database cursor,
1546 @param uid: the current user’s ID for security checks,
1547 @param id: calendar's ID.
1548 @param value: Get Value
1549 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1550 @param context: A standard dictionary for contextual values
1553 assert name == 'date'
1554 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1557 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1558 string='Duration', store=True, type='datetime'),
1559 'duration': fields.integer('Duration'),
1567 class ir_attachment(osv.osv):
1568 _name = 'ir.attachment'
1569 _inherit = 'ir.attachment'
1571 def search_count(self, cr, user, args, context=None):
1573 @param self: The object pointer
1574 @param cr: the current row, from the database cursor,
1575 @param user: the current user’s ID for security checks,
1576 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1577 @param context: A standard dictionary for contextual values
1582 args1.append(map(lambda x:str(x).split('-')[0], arg))
1583 return super(ir_attachment, self).search_count(cr, user, args1, context)
1587 def create(self, cr, uid, vals, context=None):
1589 id = context.get('default_res_id', False)
1590 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1591 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1593 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1594 context=None, count=False):
1596 @param self: The object pointer
1597 @param cr: the current row, from the database cursor,
1598 @param uid: the current user’s ID for security checks,
1599 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1600 @param offset: The Number of Results to pass,
1601 @param limit: The Number of Results to Return,
1602 @param context: A standard dictionary for contextual values
1606 for i, arg in enumerate(new_args):
1607 if arg[0] == 'res_id':
1608 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1610 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1611 limit=limit, order=order, context=context, count=False)
1614 class ir_values(osv.osv):
1615 _inherit = 'ir.values'
1617 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1618 isobject=False, meta=False, preserve_user=False, company=False):
1621 @param self: The object pointer
1622 @param cr: the current row, from the database cursor,
1623 @param uid: the current user’s ID for security checks,
1624 @param model: Get The Model
1629 if type(data) in (list, tuple):
1630 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1632 new_model.append(data)
1633 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1634 value, replace, isobject, meta, preserve_user, company)
1636 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1637 res_id_req=False, without_user=True, key2_req=True):
1640 @param self: The object pointer
1641 @param cr: the current row, from the database cursor,
1642 @param uid: the current user’s ID for security checks,
1643 @param model: Get The Model
1649 if type(data) in (list, tuple):
1650 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1652 new_model.append(data)
1653 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1654 meta, context, res_id_req, without_user, key2_req)
1658 class ir_model(osv.osv):
1660 _inherit = 'ir.model'
1662 def read(self, cr, uid, ids, fields=None, context=None,
1663 load='_classic_read'):
1665 Overrides orm read method.
1666 @param self: The object pointer
1667 @param cr: the current row, from the database cursor,
1668 @param uid: the current user’s ID for security checks,
1669 @param ids: List of IR Model’s IDs.
1670 @param context: A standard dictionary for contextual values
1672 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1675 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1676 context=context, load=load)
1679 val['id'] = base_calendar_id2real_id(val['id'])
1680 return isinstance(ids, (str, int, long)) and data[0] or data
1684 class virtual_report_spool(web_services.report_spool):
1686 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1689 @param self: The object pointer
1690 @param db: get the current database,
1691 @param uid: the current user’s ID for security checks,
1692 @param context: A standard dictionary for contextual values
1695 if object == 'printscreen.list':
1696 return super(virtual_report_spool, self).exp_report(db, uid, \
1697 object, ids, datas, context)
1700 new_ids.append(base_calendar_id2real_id(id))
1701 if datas.get('id', False):
1702 datas['id'] = base_calendar_id2real_id(datas['id'])
1703 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1705 virtual_report_spool()
1707 class res_users(osv.osv):
1708 _inherit = 'res.users'
1710 def _get_user_avail(self, cr, uid, ids, context=None):
1712 Get User Availability
1713 @param self: The object pointer
1714 @param cr: the current row, from the database cursor,
1715 @param uid: the current user’s ID for security checks,
1716 @param ids: List of res user’s IDs.
1717 @param context: A standard dictionary for contextual values
1720 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1722 attendee_obj = self.pool.get('calendar.attendee')
1723 attendee_ids = attendee_obj.search(cr, uid, [
1724 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1725 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1728 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1729 user_id = attendee_data['user_id']
1731 res.update({user_id:status})
1733 #TOCHECK: Delegated Event
1735 if user_id not in res:
1736 res[user_id] = 'free'
1740 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1742 Get User Availability Function
1743 @param self: The object pointer
1744 @param cr: the current row, from the database cursor,
1745 @param uid: the current user’s ID for security checks,
1746 @param ids: List of res user’s IDs.
1747 @param context: A standard dictionary for contextual values
1750 return self._get_user_avail(cr, uid, ids, context=context)
1753 'availability': fields.function(_get_user_avail_fun, type='selection', \
1754 selection=[('free', 'Free'), ('busy', 'Busy')], \
1755 string='Free/Busy', method=True),
1761 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: