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'))
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 event_datas = self.read(cr, uid, [id], ['date', 'rrule', 'exdate'], context=context)[0]
1434 event_id = event_datas['id']
1436 if self.get_edit_all(cr, uid, event_id, vals=None):
1437 event_id = base_calendar_id2real_id(event_id)
1439 if isinstance(event_id, (int, long)):
1440 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1441 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1442 self.unlink_events(cr, uid, [event_id], context=context)
1444 str_event, date_new = event_id.split('-')
1445 event_id = int(str_event)
1446 if event_datas['rrule']:
1447 # Remove one of the recurrent event
1448 date_new = time.strftime("%Y%m%dT%H%M%S", \
1449 time.strptime(date_new, "%Y%m%d%H%M%S"))
1450 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1451 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1453 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1454 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1455 self.unlink_events(cr, uid, [event_id], context=context)
1458 def create(self, cr, uid, vals, context=None):
1462 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1463 vals['vtimezone'] = vals['vtimezone'][40:]
1465 updated_vals = self.onchange_dates(cr, uid, [],
1466 vals.get('date', False),
1467 vals.get('duration', False),
1468 vals.get('date_deadline', False),
1469 vals.get('allday', False),
1471 vals.update(updated_vals.get('value', {}))
1472 res = super(calendar_event, self).create(cr, uid, vals, context)
1473 alarm_obj = self.pool.get('res.alarm')
1474 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1480 def do_tentative(self, cr, uid, ids, context=None, *args):
1481 """ Makes event invitation as Tentative
1482 @param self: The object pointer
1483 @param cr: the current row, from the database cursor,
1484 @param uid: the current user’s ID for security checks,
1485 @param ids: List of Event IDs
1486 @param *args: Get Tupple value
1487 @param context: A standard dictionary for contextual values
1489 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1491 def do_cancel(self, cr, uid, ids, context=None, *args):
1492 """ Makes event invitation as Tentative
1493 @param self: The object pointer
1494 @param cr: the current row, from the database cursor,
1495 @param uid: the current user’s ID for security checks,
1496 @param ids: List of Event IDs
1497 @param *args: Get Tupple value
1498 @param context: A standard dictionary for contextual values
1500 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1502 def do_confirm(self, cr, uid, ids, context=None, *args):
1503 """ Makes event invitation as Tentative
1504 @param self: The object pointer
1505 @param cr: the current row, from the database cursor,
1506 @param uid: the current user’s ID for security checks,
1507 @param ids: List of Event IDs
1508 @param *args: Get Tupple value
1509 @param context: A standard dictionary for contextual values
1511 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1515 class calendar_todo(osv.osv):
1516 """ Calendar Task """
1518 _name = "calendar.todo"
1519 _inherit = "calendar.event"
1520 _description = "Calendar Task"
1522 def _get_date(self, cr, uid, ids, name, arg, context=None):
1525 @param self: The object pointer
1526 @param cr: the current row, from the database cursor,
1527 @param uid: the current user’s ID for security checks,
1528 @param ids: List of calendar todo's IDs.
1529 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1530 @param context: A standard dictionary for contextual values
1534 for event in self.browse(cr, uid, ids, context=context):
1535 res[event.id] = event.date_start
1538 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1541 @param self: The object pointer
1542 @param cr: the current row, from the database cursor,
1543 @param uid: the current user’s ID for security checks,
1544 @param id: calendar's ID.
1545 @param value: Get Value
1546 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1547 @param context: A standard dictionary for contextual values
1550 assert name == 'date'
1551 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1554 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1555 string='Duration', store=True, type='datetime'),
1556 'duration': fields.integer('Duration'),
1564 class ir_attachment(osv.osv):
1565 _name = 'ir.attachment'
1566 _inherit = 'ir.attachment'
1568 def search_count(self, cr, user, args, context=None):
1570 @param self: The object pointer
1571 @param cr: the current row, from the database cursor,
1572 @param user: the current user’s ID for security checks,
1573 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1574 @param context: A standard dictionary for contextual values
1579 args1.append(map(lambda x:str(x).split('-')[0], arg))
1580 return super(ir_attachment, self).search_count(cr, user, args1, context)
1584 def create(self, cr, uid, vals, context=None):
1586 id = context.get('default_res_id', False)
1587 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1588 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1590 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1591 context=None, count=False):
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 args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1597 @param offset: The Number of Results to pass,
1598 @param limit: The Number of Results to Return,
1599 @param context: A standard dictionary for contextual values
1603 for i, arg in enumerate(new_args):
1604 if arg[0] == 'res_id':
1605 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1607 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1608 limit=limit, order=order, context=context, count=False)
1611 class ir_values(osv.osv):
1612 _inherit = 'ir.values'
1614 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1615 isobject=False, meta=False, preserve_user=False, company=False):
1618 @param self: The object pointer
1619 @param cr: the current row, from the database cursor,
1620 @param uid: the current user’s ID for security checks,
1621 @param model: Get The Model
1626 if type(data) in (list, tuple):
1627 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1629 new_model.append(data)
1630 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1631 value, replace, isobject, meta, preserve_user, company)
1633 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1634 res_id_req=False, without_user=True, key2_req=True):
1637 @param self: The object pointer
1638 @param cr: the current row, from the database cursor,
1639 @param uid: the current user’s ID for security checks,
1640 @param model: Get The Model
1646 if type(data) in (list, tuple):
1647 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1649 new_model.append(data)
1650 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1651 meta, context, res_id_req, without_user, key2_req)
1655 class ir_model(osv.osv):
1657 _inherit = 'ir.model'
1659 def read(self, cr, uid, ids, fields=None, context=None,
1660 load='_classic_read'):
1662 Overrides orm read method.
1663 @param self: The object pointer
1664 @param cr: the current row, from the database cursor,
1665 @param uid: the current user’s ID for security checks,
1666 @param ids: List of IR Model’s IDs.
1667 @param context: A standard dictionary for contextual values
1669 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1672 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1673 context=context, load=load)
1676 val['id'] = base_calendar_id2real_id(val['id'])
1677 return isinstance(ids, (str, int, long)) and data[0] or data
1681 class virtual_report_spool(web_services.report_spool):
1683 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1686 @param self: The object pointer
1687 @param db: get the current database,
1688 @param uid: the current user’s ID for security checks,
1689 @param context: A standard dictionary for contextual values
1692 if object == 'printscreen.list':
1693 return super(virtual_report_spool, self).exp_report(db, uid, \
1694 object, ids, datas, context)
1697 new_ids.append(base_calendar_id2real_id(id))
1698 if datas.get('id', False):
1699 datas['id'] = base_calendar_id2real_id(datas['id'])
1700 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1702 virtual_report_spool()
1704 class res_users(osv.osv):
1705 _inherit = 'res.users'
1707 def _get_user_avail(self, cr, uid, ids, context=None):
1709 Get User Availability
1710 @param self: The object pointer
1711 @param cr: the current row, from the database cursor,
1712 @param uid: the current user’s ID for security checks,
1713 @param ids: List of res user’s IDs.
1714 @param context: A standard dictionary for contextual values
1717 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1719 attendee_obj = self.pool.get('calendar.attendee')
1720 attendee_ids = attendee_obj.search(cr, uid, [
1721 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1722 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1725 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1726 user_id = attendee_data['user_id']
1728 res.update({user_id:status})
1730 #TOCHECK: Delegated Event
1732 if user_id not in res:
1733 res[user_id] = 'free'
1737 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1739 Get User Availability Function
1740 @param self: The object pointer
1741 @param cr: the current row, from the database cursor,
1742 @param uid: the current user’s ID for security checks,
1743 @param ids: List of res user’s IDs.
1744 @param context: A standard dictionary for contextual values
1747 return self._get_user_avail(cr, uid, ids, context=context)
1750 'availability': fields.function(_get_user_avail_fun, type='selection', \
1751 selection=[('free', 'Free'), ('busy', 'Busy')], \
1752 string='Free/Busy', method=True),
1758 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: