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 osv import fields, osv
26 from service import web_services
27 from tools.translate import _
34 1: "January", 2: "February", 3: "March", 4: "April", \
35 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
36 10: "October", 11: "November", 12: "December"
39 def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
41 Get recurrent dates based on Rule string considering exdate and start date
42 @param rrulestring: Rulestring
43 @param exdate: List of exception dates for rrule
44 @param startdate: Startdate for computing recurrent dates
45 @return: List of Recurrent dates
48 val = parser.parse(''.join((re.compile('\d')).findall(date)))
52 startdate = datetime.now()
55 rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
58 datetime_obj = todate(date)
59 rset1._exdate.append(datetime_obj)
61 rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
63 return list(rset1._iter())
65 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
67 This function converts virtual event id into real id of actual event
68 @param base_calendar_id: Id of calendar
69 @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
72 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
73 res = base_calendar_id.split('-')
78 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
79 time.strptime(res[1], "%Y%m%d%H%M%S"))
80 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
81 end = start + timedelta(hours=with_date)
82 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
85 return base_calendar_id and int(base_calendar_id) or base_calendar_id
87 def real_id2base_calendar_id(real_id, recurrent_date):
89 Convert real id of record into virtual id using recurrent_date
90 e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
92 @return: real id with recurrent date.
95 if real_id and recurrent_date:
96 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
97 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
98 return '%d-%s' % (real_id, recurrent_date)
101 def _links_get(self, cr, uid, context=None):
104 @param cr: the current row, from the database cursor,
105 @param uid: the current user’s ID for security checks,
106 @param context: A standard dictionary for contextual values
107 @return: list of dictionary which contain object and name and id.
109 obj = self.pool.get('res.request.link')
110 ids = obj.search(cr, uid, [])
111 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
112 return [(r['object'], r['name']) for r in res]
114 html_invitation = """
117 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
118 <title>%(name)s</title>
121 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
122 style="font-family: Arial, Sans-serif; font-size: 14">
124 <td width="100%%">Hello,</td>
127 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
130 <td width="100%%">Below are the details of event:</td>
134 <table cellspacing="0" cellpadding="5" border="0" summary=""
135 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
136 <tr valign="center" align="center">
137 <td bgcolor="DFDFDF">
143 <table cellpadding="8" cellspacing="0" border="0"
144 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
148 <div><b>Start Date</b></div>
151 <td>%(start_date)s</td>
153 <div><b>End Date</b></div>
156 <td width="25%%">%(end_date)s</td>
159 <td><b>Description</b></td>
161 <td colspan="3">%(description)s</td>
165 <div><b>Location</b></div>
168 <td colspan="3">%(location)s</td>
172 <div><b>Event Attendees</b></div>
177 <div>%(attendees)s</div>
185 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
186 style="font-family: Arial, Sans-serif; font-size: 14">
188 <td width="100%%">From:</td>
191 <td width="100%%">%(user)s</td>
194 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
197 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
204 class calendar_attendee(osv.osv):
206 Calendar Attendee Information
208 _name = 'calendar.attendee'
209 _description = 'Attendee information'
214 def _get_address(self, name=None, email=None):
216 Gives email information in ical CAL-ADDRESS type format
217 @param name: Name for CAL-ADDRESS value
218 @param email: Email address for CAL-ADDRESS value
222 return (name or '') + (email and ('MAILTO:' + email) or '')
224 def _compute_data(self, cr, uid, ids, name, arg, context=None):
226 Compute data on function fields for attendee values .
227 @param cr: the current row, from the database cursor,
228 @param uid: the current user’s ID for security checks,
229 @param ids: List of calendar attendee’s IDs.
230 @param name: name of field.
231 @param context: A standard dictionary for contextual values
232 @return: Dictionary of form {id: {'field Name': value'}}.
236 for attdata in self.browse(cr, uid, ids, context=context):
239 if name == 'sent_by':
240 if not attdata.sent_by_uid:
241 result[id][name] = ''
244 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
245 attdata.sent_by_uid.address_id.email)
249 result[id][name] = attdata.user_id.name
250 elif attdata.partner_address_id:
251 result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
253 result[id][name] = attdata.email or ''
255 if name == 'delegated_to':
257 for child in attdata.child_ids:
259 todata.append('MAILTO:' + child.email)
260 result[id][name] = ', '.join(todata)
262 if name == 'delegated_from':
264 for parent in attdata.parent_ids:
266 fromdata.append('MAILTO:' + parent.email)
267 result[id][name] = ', '.join(fromdata)
269 if name == 'event_date':
271 result[id][name] = attdata.ref.date
273 result[id][name] = False
275 if name == 'event_end_date':
277 result[id][name] = attdata.ref.date_deadline
279 result[id][name] = False
281 if name == 'sent_by_uid':
283 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
285 result[id][name] = uid
287 if name == 'language':
288 user_obj = self.pool.get('res.users')
289 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
290 result[id][name] = lang.replace('_', '-')
294 def _links_get(self, cr, uid, context=None):
296 Get request link for ref field in calendar attendee.
297 @param cr: the current row, from the database cursor,
298 @param uid: the current user’s ID for security checks,
299 @param context: A standard dictionary for contextual values
300 @return: list of dictionary which contain object and name and id.
302 obj = self.pool.get('res.request.link')
303 ids = obj.search(cr, uid, [])
304 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
305 return [(r['object'], r['name']) for r in res]
307 def _lang_get(self, cr, uid, context=None):
309 Get language for language selection field.
310 @param cr: the current row, from the database cursor,
311 @param uid: the current user’s ID for security checks,
312 @param context: A standard dictionary for contextual values
313 @return: list of dictionary which contain code and name and id.
315 obj = self.pool.get('res.lang')
316 ids = obj.search(cr, uid, [])
317 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
318 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
322 'cutype': fields.selection([('individual', 'Individual'), \
323 ('group', 'Group'), ('resource', 'Resource'), \
324 ('room', 'Room'), ('unknown', '') ], \
325 'Invite Type', help="Specify the type of Invitation"),
326 'member': fields.char('Member', size=124,
327 help="Indicate the groups that the attendee belongs to"),
328 'role': fields.selection([('req-participant', 'Participation required'), \
329 ('chair', 'Chair Person'), \
330 ('opt-participant', 'Optional Participation'), \
331 ('non-participant', 'For information Purpose')], 'Role', \
332 help='Participation role for the calendar user'),
333 'state': fields.selection([('tentative', 'Tentative'),
334 ('needs-action', 'Needs Action'),
335 ('accepted', 'Accepted'),
336 ('declined', 'Declined'),
337 ('delegated', 'Delegated')], 'State', readonly=True, \
338 help="Status of the attendee's participation"),
339 'rsvp': fields.boolean('Required Reply?',
340 help="Indicats whether the favor of a reply is requested"),
341 'delegated_to': fields.function(_compute_data, method=True, \
342 string='Delegated To', type="char", size=124, store=True, \
343 multi='delegated_to', help="The users that the original \
344 request was delegated to"),
345 'delegated_from': fields.function(_compute_data, method=True, string=\
346 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
347 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
348 'attendee_id', 'parent_id', 'Delegrated From'),
349 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
350 'attendee_id', 'child_id', 'Delegrated To'),
351 'sent_by': fields.function(_compute_data, method=True, string='Sent By', \
352 type="char", multi='sent_by', store=True, size=124, \
353 help="Specify the user that is acting on behalf of the calendar user"),
354 'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', \
355 type="many2one", relation="res.users", multi='sent_by_uid'),
356 'cn': fields.function(_compute_data, method=True, string='Common name', \
357 type="char", size=124, multi='cn', store=True),
358 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
359 that points to the directory information corresponding to the attendee."),
360 'language': fields.function(_compute_data, method=True, string='Language', \
361 type="selection", selection=_lang_get, multi='language', \
362 store=True, help="To specify the language for text values in a\
363 property or property parameter."),
364 'user_id': fields.many2one('res.users', 'User'),
365 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
366 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
367 relation='res.partner', string='Partner', help="Partner related to contact"),
368 'email': fields.char('Email', size=124, help="Email of Invited Person"),
369 'event_date': fields.function(_compute_data, method=True, string='Event Date', \
370 type="datetime", multi='event_date'),
371 'event_end_date': fields.function(_compute_data, method=True, \
372 string='Event End Date', type="datetime", \
373 multi='event_end_date'),
374 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
375 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
378 'state': 'needs-action',
379 'role': 'req-participant',
381 'cutype': 'individual',
384 def copy(self, cr, uid, id, default=None, context=None):
385 raise osv.except_osv(_('Warning!'), _('Can not Duplicate'))
387 def get_ics_file(self, cr, uid, event_obj, context=None):
389 Returns iCalendar file for the event invitation
390 @param self: The object pointer
391 @param cr: the current row, from the database cursor,
392 @param uid: the current user’s ID for security checks,
393 @param event_obj: Event object (browse record)
394 @param context: A standard dictionary for contextual values
395 @return: .ics file content
398 def ics_datetime(idate, short=False):
400 if short or len(idate)<=10:
401 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
403 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
407 # FIXME: why isn't this in CalDAV?
411 cal = vobject.iCalendar()
412 event = cal.add('vevent')
413 if not event_obj.date_deadline or not event_obj.date:
414 raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
415 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
416 event.add('dtstart').value = ics_datetime(event_obj.date)
417 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
418 event.add('summary').value = event_obj.name
419 if event_obj.description:
420 event.add('description').value = event_obj.description
421 if event_obj.location:
422 event.add('location').value = event_obj.location
424 event.add('rrule').value = event_obj.rrule
425 if event_obj.organizer:
426 event_org = event.add('organizer')
427 event_org.params['CN'] = [event_obj.organizer]
428 event_org.value = 'MAILTO:' + (event_obj.organizer)
429 elif event_obj.user_id or event_obj.organizer_id:
430 event_org = event.add('organizer')
431 organizer = event_obj.organizer_id
433 organizer = event_obj.user_id
434 event_org.params['CN'] = [organizer.name]
435 event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
437 if event_obj.alarm_id:
438 # computes alarm data
439 valarm = event.add('valarm')
440 alarm_object = self.pool.get('res.alarm')
441 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
442 # Compute trigger data
443 interval = alarm_data['trigger_interval']
444 occurs = alarm_data['trigger_occurs']
445 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
446 or -(alarm_data['trigger_duration'])
447 related = alarm_data['trigger_related']
448 trigger = valarm.add('TRIGGER')
449 trigger.params['related'] = [related.upper()]
450 if interval == 'days':
451 delta = timedelta(days=duration)
452 if interval == 'hours':
453 delta = timedelta(hours=duration)
454 if interval == 'minutes':
455 delta = timedelta(minutes=duration)
456 trigger.value = delta
457 # Compute other details
458 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
460 for attendee in event_obj.attendee_ids:
461 attendee_add = event.add('attendee')
462 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
463 attendee_add.params['ROLE'] = [str(attendee.role)]
464 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
465 attendee_add.value = 'MAILTO:' + (attendee.email or '')
466 res = cal.serialize()
469 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
471 Send mail for event invitation to event attendees.
472 @param cr: the current row, from the database cursor,
473 @param uid: the current user’s ID for security checks,
474 @param ids: List of attendee’s IDs.
475 @param email_from: Email address for user sending the mail
476 @param context: A standard dictionary for contextual values
482 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
483 for att in self.browse(cr, uid, ids, context=context):
484 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
485 sign = '<br>'.join(sign and sign.split('\n') or [])
490 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
492 for att2 in self.browse(cr, uid, other_invitation_ids):
493 att_infos.append(((att2.user_id and att2.user_id.name) or \
494 (att2.partner_id and att2.partner_id.name) or \
495 att2.email) + ' - Status: ' + att2.state.title())
496 body_vals = {'name': res_obj.name,
497 'start_date': res_obj.date,
498 'end_date': res_obj.date_deadline or False,
499 'description': res_obj.description or '-',
500 'location': res_obj.location or '-',
501 'attendees': '<br>'.join(att_infos),
502 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
506 body = html_invitation % body_vals
507 if mail_to and email_from:
508 attach = self.get_ics_file(cr, uid, res_obj, context=context)
514 attach=attach and [('invitation.ics', attach)] or None,
520 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
522 Make entry on email and availbility on change of user_id field.
523 @param cr: the current row, from the database cursor,
524 @param uid: the current user’s ID for security checks,
525 @param ids: List of calendar attendee’s IDs.
526 @param user_id: Changed value of User id
527 @return: dictionary of value. which put value in email and availability fields.
531 return {'value': {'email': ''}}
532 usr_obj = self.pool.get('res.users')
533 user = usr_obj.browse(cr, uid, user_id, *args)
534 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
536 def do_tentative(self, cr, uid, ids, context=None, *args):
537 """ Makes event invitation as Tentative
538 @param self: The object pointer
539 @param cr: the current row, from the database cursor,
540 @param uid: the current user’s ID for security checks,
541 @param ids: List of calendar attendee’s IDs
542 @param *args: Get Tupple value
543 @param context: A standard dictionary for contextual values
545 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
547 def do_accept(self, cr, uid, ids, context=None, *args):
549 Update state of invitation as Accepted and
550 if the invited user is other then event user it will make a copy of this event for invited user
551 @param cr: the current row, from the database cursor,
552 @param uid: the current user’s ID for security checks,
553 @param ids: List of calendar attendee’s IDs.
554 @param context: A standard dictionary for contextual values
560 for vals in self.browse(cr, uid, ids, context=context):
561 if vals.ref and vals.ref.user_id:
562 mod_obj = self.pool.get(vals.ref._name)
563 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
564 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
565 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
569 def do_decline(self, cr, uid, ids, context=None, *args):
570 """ Marks event invitation as Declined
571 @param self: The object pointer
572 @param cr: the current row, from the database cursor,
573 @param uid: the current user’s ID for security checks,
574 @param ids: List of calendar attendee’s IDs
575 @param *args: Get Tupple value
576 @param context: A standard dictionary for contextual values """
579 return self.write(cr, uid, ids, {'state': 'declined'}, context)
581 def create(self, cr, uid, vals, context=None):
582 """ Overrides orm create method.
583 @param self: The object pointer
584 @param cr: the current row, from the database cursor,
585 @param uid: the current user’s ID for security checks,
586 @param vals: Get Values
587 @param context: A standard dictionary for contextual values """
591 if not vals.get("email") and vals.get("cn"):
592 cnval = vals.get("cn").split(':')
593 email = filter(lambda x:x.__contains__('@'), cnval)
594 vals['email'] = email and email[0] or ''
595 vals['cn'] = vals.get("cn")
596 res = super(calendar_attendee, self).create(cr, uid, vals, context)
600 class res_alarm(osv.osv):
601 """Resource Alarm """
603 _description = 'Basic Alarm Information'
606 'name':fields.char('Name', size=256, required=True),
607 'trigger_occurs': fields.selection([('before', 'Before'), \
608 ('after', 'After')], \
609 'Triggers', required=True),
610 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
611 ('hours', 'Hours'), \
612 ('days', 'Days')], 'Interval', \
614 'trigger_duration': fields.integer('Duration', required=True),
615 'trigger_related': fields.selection([('start', 'The event starts'), \
616 ('end', 'The event ends')], \
617 'Related to', required=True),
618 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
619 are both optional, but if one occurs, so MUST the other"""),
620 'repeat': fields.integer('Repeat'),
621 'active': fields.boolean('Active', help="If the active field is set to \
622 true, it will allow you to hide the event alarm information without removing it.")
625 'trigger_interval': 'minutes',
626 'trigger_duration': 5,
627 'trigger_occurs': 'before',
628 'trigger_related': 'start',
632 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
634 Create Alarm for event.
635 @param cr: the current row, from the database cursor,
636 @param uid: the current user’s ID for security checks,
637 @param ids: List of res alarm’s IDs.
638 @param model: Model name.
639 @param date: Event date
640 @param context: A standard dictionary for contextual values
645 alarm_obj = self.pool.get('calendar.alarm')
646 res_alarm_obj = self.pool.get('res.alarm')
647 ir_obj = self.pool.get('ir.model')
648 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
650 model_obj = self.pool.get(model)
651 for data in model_obj.browse(cr, uid, ids, context=context):
653 basic_alarm = data.alarm_id
654 cal_alarm = data.base_calendar_alarm_id
655 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
657 # Find for existing res.alarm
658 duration = cal_alarm.trigger_duration
659 interval = cal_alarm.trigger_interval
660 occurs = cal_alarm.trigger_occurs
661 related = cal_alarm.trigger_related
662 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
663 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
666 'trigger_duration': duration,
667 'trigger_interval': interval,
668 'trigger_occurs': occurs,
669 'trigger_related': related,
670 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
672 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
674 new_res_alarm = alarm_ids[0]
675 cr.execute('UPDATE %s ' % model_obj._table + \
676 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
678 (cal_alarm.id, new_res_alarm, data.id))
680 self.do_alarm_unlink(cr, uid, [data.id], model)
684 'description': data.description,
686 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
687 'trigger_related': basic_alarm.trigger_related,
688 'trigger_duration': basic_alarm.trigger_duration,
689 'trigger_occurs': basic_alarm.trigger_occurs,
690 'trigger_interval': basic_alarm.trigger_interval,
691 'duration': basic_alarm.duration,
692 'repeat': basic_alarm.repeat,
694 'event_date': data[date],
696 'model_id': model_id,
699 alarm_id = alarm_obj.create(cr, uid, vals)
700 cr.execute('UPDATE %s ' % model_obj._table + \
701 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
703 ( alarm_id, basic_alarm.id, data.id) )
706 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
708 Delete alarm specified in ids
709 @param cr: the current row, from the database cursor,
710 @param uid: the current user’s ID for security checks,
711 @param ids: List of res alarm’s IDs.
712 @param model: Model name for which alarm is to be cleared.
717 alarm_obj = self.pool.get('calendar.alarm')
718 ir_obj = self.pool.get('ir.model')
719 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
720 model_obj = self.pool.get(model)
721 for datas in model_obj.browse(cr, uid, ids, context=context):
722 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
724 alarm_obj.unlink(cr, uid, alarm_ids)
725 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
726 where id=%%s' % model_obj._table,(datas.id,))
731 class calendar_alarm(osv.osv):
732 _name = 'calendar.alarm'
733 _description = 'Event alarm information'
734 _inherit = 'res.alarm'
738 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
739 'name': fields.char('Summary', size=124, help="""Contains the text to be \
740 used as the message subject for email \
741 or contains the text to be used for display"""),
742 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
743 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
744 required=True, help="Defines the action to be invoked when an alarm is triggered"),
745 'description': fields.text('Description', help='Provides a more complete \
746 description of the calendar component, than that \
747 provided by the "SUMMARY" property'),
748 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
749 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
750 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
751 which is rendered when the alarm is triggered for audio,
752 * File which is intended to be sent as message attachments for email,
753 * Points to a procedure resource, which is invoked when\
754 the alarm is triggered for procedure."""),
755 'res_id': fields.integer('Resource ID'),
756 'model_id': fields.many2one('ir.model', 'Model'),
757 'user_id': fields.many2one('res.users', 'Owner'),
758 'event_date': fields.datetime('Event Date'),
759 'event_end_date': fields.datetime('Event End Date'),
760 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
761 'state':fields.selection([
766 ], 'State', select=True, readonly=True),
774 def create(self, cr, uid, vals, context=None):
776 Overrides orm create method.
777 @param self: The object pointer
778 @param cr: the current row, from the database cursor,
779 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
780 @param context: A standard dictionary for contextual values
781 @return: new record id for calendar_alarm.
785 event_date = vals.get('event_date', False)
787 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
788 if vals['trigger_interval'] == 'days':
789 delta = timedelta(days=vals['trigger_duration'])
790 if vals['trigger_interval'] == 'hours':
791 delta = timedelta(hours=vals['trigger_duration'])
792 if vals['trigger_interval'] == 'minutes':
793 delta = timedelta(minutes=vals['trigger_duration'])
794 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
795 vals['trigger_date'] = trigger_date
796 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
799 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
801 """Scheduler for event reminder
802 @param self: The object pointer
803 @param cr: the current row, from the database cursor,
804 @param uid: the current user’s ID for security checks,
805 @param ids: List of calendar alarm’s IDs.
806 @param use_new_cursor: False or the dbname
807 @param context: A standard dictionary for contextual values
809 return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
812 current_datetime = datetime.now()
813 request_obj = self.pool.get('res.request')
814 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
818 for alarm in self.browse(cr, uid, alarm_ids, context=context):
819 next_trigger_date = None
821 model_obj = self.pool.get(alarm.model_id.model)
822 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
826 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
827 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
829 trigger_interval = alarm.trigger_interval
830 if trigger_interval == 'days':
831 delta = timedelta(days=alarm.trigger_duration)
832 if trigger_interval == 'hours':
833 delta = timedelta(hours=alarm.trigger_duration)
834 if trigger_interval == 'minutes':
835 delta = timedelta(minutes=alarm.trigger_duration)
836 delta = alarm.trigger_occurs == 'after' and delta or -delta
838 for rdate in recurrent_dates:
839 if rdate + delta > current_datetime:
841 if rdate + delta <= current_datetime:
842 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
843 rest_dates = recurrent_dates[len(re_dates):]
844 next_trigger_date = rest_dates and rest_dates[0] or None
847 re_dates = [alarm.trigger_date]
849 for r_date in re_dates:
850 ref = alarm.model_id.model + ',' + str(alarm.res_id)
852 # search for alreay sent requests
853 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
856 if alarm.action == 'display':
859 'act_from': alarm.user_id.id,
860 'act_to': alarm.user_id.id,
861 'body': alarm.description,
862 'trigger_date': r_date,
865 request_id = request_obj.create(cr, uid, value)
866 request_ids = [request_id]
867 for attendee in res_obj.attendee_ids:
869 value['act_to'] = attendee.user_id.id
870 request_id = request_obj.create(cr, uid, value)
871 request_ids.append(request_id)
872 request_obj.request_send(cr, uid, request_ids)
874 if alarm.action == 'email':
875 sub = '[Openobject Reminder] %s' % (alarm.name)
887 """ % (alarm.name, alarm.trigger_date, alarm.description, \
888 alarm.user_id.name, alarm.user_id.signature)
889 mail_to = [alarm.user_id.address_id.email]
890 for att in alarm.attendee_ids:
891 mail_to.append(att.user_id.address_id.email)
894 tools.config.get('email_from', False),
899 if next_trigger_date:
900 update_vals.update({'trigger_date': next_trigger_date})
902 update_vals.update({'state': 'done'})
903 self.write(cr, uid, [alarm.id], update_vals)
909 class calendar_event(osv.osv):
910 _name = "calendar.event"
911 _description = "Calendar Event"
914 def _tz_get(self, cr, uid, context=None):
915 return [(x.lower(), x) for x in pytz.all_timezones]
917 def onchange_allday(self, cr, uid, ids, allday, context=None):
918 """Sets duration as 24 Hours if event is selected for all day
919 @param self: The object pointer
920 @param cr: the current row, from the database cursor,
921 @param uid: the current user’s ID for security checks,
922 @param ids: List of calendar event’s IDs.
923 @param allday: Value of allday boolean
924 @param context: A standard dictionary for contextual values
926 if not allday or not ids:
931 return {'value': value}
933 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
934 """Returns duration and/or end date based on values passed
935 @param self: The object pointer
936 @param cr: the current row, from the database cursor,
937 @param uid: the current user’s ID for security checks,
938 @param ids: List of calendar event’s IDs.
939 @param start_date: Starting date
940 @param duration: Duration between start date and end date
941 @param end_date: Ending Datee
942 @param context: A standard dictionary for contextual values
950 if not end_date and not duration:
952 value['duration'] = duration
954 if allday: # For all day event
955 value = {'duration': 24}
958 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
959 if end_date and not duration:
960 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
962 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
963 value['duration'] = round(duration, 2)
965 end = start + timedelta(hours=duration)
966 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
967 elif end_date and duration and not allday:
968 # we have both, keep them synchronized:
969 # set duration based on end_date (arbitrary decision: this avoid
970 # getting dates like 06:31:48 instead of 06:32:00)
971 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
973 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
974 value['duration'] = round(duration, 2)
976 return {'value': value}
978 def unlink_events(self, cr, uid, ids, context=None):
980 This function deletes event which are linked with the event with recurrent_uid
981 (Removes the events which refers to the same UID value)
986 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
987 r_ids = map(lambda x: x[0], cr.fetchall())
988 self.unlink(cr, uid, r_ids, context=context)
991 def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
993 Sets values of fields that defines event recurrence from the value of rrule string
994 @param self: The object pointer
995 @param cr: the current row, from the database cursor,
996 @param id: List of calendar event's ids.
997 @param context: A standard dictionary for contextual values
998 @return: dictionary of rrule value.
1002 cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
1003 mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
1004 day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
1007 cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1010 for part in value.split(';'):
1011 if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1012 rrule_type = part.lower()[5:]
1015 rrule_type = 'custom'
1017 ans = value.split(';')
1019 val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1020 if not val.get('interval'):
1021 rrule_type = 'custom'
1022 elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1023 rrule_type = 'custom'
1025 qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1026 qry_args = [ rrule_type, ]
1027 new_val = val.copy()
1028 for k, v in val.items():
1029 if val['freq'] == 'weekly' and val.get('byday'):
1030 for day in val['byday'].split(','):
1034 if val.get('until'):
1035 until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
1036 new_val['end_date'] = until.strftime('%Y-%m-%d')
1038 new_val.pop('until')
1040 if val.get('bymonthday'):
1041 new_val['day'] = val.get('bymonthday')
1042 val.pop('bymonthday')
1043 new_val['select1'] = 'date'
1044 new_val.pop('bymonthday')
1046 if val.get('byday'):
1047 d = val.get('byday')
1049 new_val['byday'] = d[:2]
1050 new_val['week_list'] = d[2:4].upper()
1052 new_val['byday'] = d[:1]
1053 new_val['week_list'] = d[1:3].upper()
1054 new_val['select1'] = 'day'
1056 if val.get('bymonth'):
1057 new_val['month_list'] = val.get('bymonth')
1059 new_val.pop('bymonth')
1061 for k, v in new_val.items():
1062 qry += ", %s=%%s" % k
1065 qry = qry + " where id=%s"
1067 cr.execute(qry, qry_args)
1070 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
1072 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
1073 @param self: The object pointer
1074 @param cr: the current row, from the database cursor,
1075 @param id: List of calendar event's ids.
1076 @param context: A standard dictionary for contextual values
1077 @return: dictionary of rrule value.
1080 for datas in self.read(cr, uid, ids, context=context):
1082 if datas.get('rrule_type'):
1083 if datas.get('rrule_type') == 'none':
1084 result[event] = False
1085 cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
1086 if datas.get('rrule_type') :
1087 if datas.get('interval', 0) < 0:
1088 raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
1089 if datas.get('count', 0) < 0:
1090 raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1091 rrule_custom = self.compute_rule_string(cr, uid, datas, \
1093 result[event] = rrule_custom
1095 result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
1097 for id, myrule in result.items():
1098 #Remove the events generated from recurrent event
1100 self.unlink_events(cr, uid, [id], context=context)
1104 'id': fields.integer('ID'),
1105 'sequence': fields.integer('Sequence'),
1106 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1107 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1108 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1109 'create_date': fields.datetime('Created', readonly=True),
1110 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1111 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1112 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1113 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1114 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1115 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1116 'Show as', states={'done': [('readonly', True)]}),
1117 'base_calendar_url': fields.char('Caldav URL', size=264),
1118 'state': fields.selection([('tentative', 'Tentative'),
1119 ('confirmed', 'Confirmed'),
1120 ('cancelled', 'Cancelled')], 'State', readonly=True),
1121 'exdate': fields.text('Exception Date/Times', help="This property \
1122 defines the list of date/time exceptions for a recurring calendar component."),
1123 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1124 rule or repeating pattern of time to exclude from the recurring rule."),
1125 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1126 string='Recurrent Rule', store=True, \
1127 fnct_inv=_set_rrulestring, help='Defines a\
1128 rule or repeating pattern for recurring events\n\
1129 e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
1130 FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
1131 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1132 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1133 ('yearly', 'Yearly'),],
1134 'Recurrency', states={'done': [('readonly', True)]},
1135 help="Let the event automatically repeat at that interval"),
1136 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1137 help="Set an alarm at this time, before the event occurs" ),
1138 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1139 'recurrent_uid': fields.integer('Recurrent ID'),
1140 'recurrent_id': fields.datetime('Recurrent ID date'),
1141 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1142 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1143 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1144 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1145 'freq': fields.selection([('None', 'No Repeat'),
1146 ('hourly', 'Hours'),
1148 ('weekly', 'Weeks'),
1149 ('monthly', 'Months'),
1150 ('yearly', 'Years'), ], 'Frequency'),
1152 'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1153 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1154 'count': fields.integer('Repeat', help="Repeat x times"),
1155 'mo': fields.boolean('Mon'),
1156 'tu': fields.boolean('Tue'),
1157 'we': fields.boolean('Wed'),
1158 'th': fields.boolean('Thu'),
1159 'fr': fields.boolean('Fri'),
1160 'sa': fields.boolean('Sat'),
1161 'su': fields.boolean('Sun'),
1162 'select1': fields.selection([('date', 'Date of month'),
1163 ('day', 'Day of month')], 'Option'),
1164 'day': fields.integer('Date of month'),
1165 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1166 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1167 ('FR', 'Friday'), ('SA', 'Saturday'), \
1168 ('SU', 'Sunday')], 'Weekday'),
1169 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1170 ('3', 'Third'), ('4', 'Fourth'), \
1171 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1172 'month_list': fields.selection(months.items(), 'Month'),
1173 'end_date': fields.date('Repeat Until'),
1174 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1175 'event_id', 'attendee_id', 'Attendees'),
1176 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1177 'active': fields.boolean('Active', help="If the active field is set to \
1178 true, it will allow you to hide the event alarm information without removing it."),
1179 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1180 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1182 def default_organizer(self, cr, uid, context=None):
1183 user_pool = self.pool.get('res.users')
1184 user = user_pool.browse(cr, uid, uid, context=context)
1187 res += " <%s>" %(user.user_email)
1191 'end_type' : 'forever',
1192 'state': 'tentative',
1199 'user_id': lambda self, cr, uid, ctx: uid,
1200 'organizer': default_organizer,
1204 def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
1209 if edit_all and rrule_type:
1211 base_calendar_id2real_id(id)
1214 def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1216 Modifies the recurring event
1217 @param cr: the current row, from the database cursor,
1218 @param uid: the current user’s ID for security checks,
1219 @param event_ids: List of crm meeting’s IDs.
1220 @param context: A standard dictionary for contextual values
1223 for event_id in event_ids:
1224 event_id = base_calendar_id2real_id(event_id)
1227 defaults.update({'table': self._table})
1229 qry = "UPDATE %(table)s set name = '%(name)s', \
1230 date = '%(date)s', date_deadline = '%(date_deadline)s'"
1231 if defaults.get('alarm_id'):
1232 qry += ", alarm_id = %(alarm_id)s"
1233 if defaults.get('location'):
1234 qry += ", location = '%(location)s'"
1235 qry += "WHERE id = %s" % (event_id)
1236 cr.execute(qry, defaults)
1240 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
1241 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1242 This method gives ids of dates that comes between start date and end date of calendar views
1243 @param self: The object pointer
1244 @param cr: the current row, from the database cursor,
1245 @param uid: the current user’s ID for security checks,
1246 @param base_start_date: Get Start Date
1247 @param base_until_date: Get End Date
1248 @param limit: The Number of Results to Return """
1252 if isinstance(select, (str, int, long)):
1258 if ids and (base_start_date or base_until_date):
1259 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1260 m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1261 " m where m.id = ANY(%s)", (ids,) )
1263 for data in cr.dictfetchall():
1264 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1265 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1266 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1267 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1268 start_date = event_date
1269 if not data['rrule']:
1270 if start_date and (event_date < start_date):
1272 if until_date and (event_date > until_date):
1274 idval = real_id2base_calendar_id(data['id'], data['date'])
1275 if not data['recurrent_id']:
1276 result.append(idval)
1278 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1279 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1280 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1281 if ls[1] == data['recurrent_id']:
1282 result.append(idval)
1283 recur_dict.append(ex_id)
1285 exdate = data['exdate'] and data['exdate'].split(',') or []
1286 rrule_str = data['rrule']
1288 rrule_until_date = False
1290 for rule in rrule_str.split(';'):
1291 name, value = rule.split('=')
1294 value = parser.parse(value)
1295 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1296 if until_date and until_date >= rrule_until_date:
1297 until_date = rrule_until_date
1299 value = until_date.strftime("%Y%m%d%H%M%S")
1300 new_rule = '%s=%s' % (name, value)
1301 new_rrule_str.append(new_rule)
1302 if not is_until and until_date:
1303 value = until_date.strftime("%Y%m%d%H%M%S")
1305 new_rule = '%s=%s' % (name, value)
1306 new_rrule_str.append(new_rule)
1307 new_rrule_str = ';'.join(new_rrule_str)
1308 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1310 for r_date in rdates:
1311 if start_date and r_date < start_date:
1313 if until_date and r_date > until_date:
1315 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1316 result.append(idval)
1318 if count >= limit: # do not generate more than 'limit' recurring events
1321 ids = list(set(result)-set(recur_dict))
1322 if isinstance(select, (str, int, long)):
1323 return ids and ids[0] or False
1326 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1328 Compute rule string according to value type RECUR of iCalendar from the values given.
1329 @param self: the object pointer
1330 @param cr: the current row, from the database cursor,
1331 @param uid: the current user’s ID for security checks,
1332 @param datas: dictionary of freq and interval value.
1333 @param context: A standard dictionary for contextual values
1334 @return: String value of the format RECUR of iCalendar
1337 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1341 freq=datas.get('rrule_type')
1345 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1347 if freq == 'weekly':
1348 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1350 weekstring = ';BYDAY=' + ','.join(byday)
1352 elif freq == 'monthly':
1353 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1354 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1355 if datas.get('select1')=='day':
1356 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1357 elif datas.get('select1')=='date':
1358 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1361 if datas.get('end_date'):
1362 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1363 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1364 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1366 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1367 + enddate + monthstring + yearstring
1371 def search(self, cr, uid, args, offset=0, limit=100, order=None,
1372 context=None, count=False):
1374 Overrides orm search method.
1375 @param cr: the current row, from the database cursor,
1376 @param user: the current user’s ID for security checks,
1377 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1378 @param offset: The Number of Results to Pass
1379 @param limit: The Number of Results to Return
1380 @param context: A standard dictionary for contextual values
1381 @param count: If its True the method returns number of records instead of ids
1384 args_without_date = []
1389 if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1390 args_without_date.append(arg)
1392 if arg[1] in ('>', '>='):
1396 elif arg[1] in ('<', '<='):
1400 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1401 offset, limit, order, context, count)
1403 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
1407 def get_edit_all(self, cr, uid, id, vals=None):
1409 return true if we have to edit all meeting from the same recurrent
1410 or only on occurency
1412 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1413 if(vals and 'edit_all' in vals): #we jsut check edit_all
1414 return vals['edit_all']
1415 else: #it's a recurrent event and edit_all is already check
1416 return meeting['recurrency'] and meeting['edit_all']
1421 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1423 Overrides orm write method.
1424 @param self: the object pointer
1425 @param cr: the current row, from the database cursor,
1426 @param uid: the current user’s ID for security checks,
1427 @param ids: List of crm meeting's ids
1428 @param vals: Dictionary of field value.
1429 @param context: A standard dictionary for contextual values
1434 if isinstance(ids, (str, int, long)):
1440 for event_id in select:
1441 real_event_id = base_calendar_id2real_id(event_id)
1444 if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1445 event_id = real_event_id
1448 if len(str(event_id).split('-')) > 1:
1449 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1450 'rrule', 'duration', 'exdate'])
1451 if data.get('rrule'):
1454 'recurrent_uid': real_event_id,
1455 'recurrent_id': data.get('date'),
1456 'rrule_type': 'none',
1459 'recurrency' : False,
1462 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1464 date_new = event_id.split('-')[1]
1465 date_new = time.strftime("%Y%m%dT%H%M%S", \
1466 time.strptime(date_new, "%Y%m%d%H%M%S"))
1467 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1468 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1470 context.update({'active_id': new_id, 'active_ids': [new_id]})
1472 if not real_event_id in new_ids:
1473 new_ids.append(real_event_id)
1475 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1476 vals['vtimezone'] = vals['vtimezone'][40:]
1478 updated_vals = self.onchange_dates(cr, uid, new_ids,
1479 vals.get('date', False),
1480 vals.get('duration', False),
1481 vals.get('date_deadline', False),
1482 vals.get('allday', False),
1484 vals.update(updated_vals.get('value', {}))
1486 if not 'edit_all' in vals:
1487 vals['edit_all'] = False
1490 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1492 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1493 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1494 # change alarm details
1495 alarm_obj = self.pool.get('res.alarm')
1496 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1499 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1501 Overrides orm browse method.
1502 @param self: the object pointer
1503 @param cr: the current row, from the database cursor,
1504 @param uid: the current user’s ID for security checks,
1505 @param ids: List of crm meeting's ids
1506 @param context: A standard dictionary for contextual values
1507 @return: the object list.
1509 if isinstance(ids, (str, int, long)):
1513 select = map(lambda x: base_calendar_id2real_id(x), select)
1514 res = super(calendar_event, self).browse(cr, uid, select, context, \
1515 list_class, fields_process)
1516 if isinstance(ids, (str, int, long)):
1517 return res and res[0] or False
1521 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1523 Overrides orm Read method.Read List of fields for calendar event.
1524 @param cr: the current row, from the database cursor,
1525 @param user: the current user’s ID for security checks,
1526 @param ids: List of calendar event's id.
1527 @param fields: List of fields.
1528 @param context: A standard dictionary for contextual values
1529 @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1531 # FIXME This whole id mangling has to go!
1535 if isinstance(ids, (str, int, long)):
1539 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1541 if fields and 'date' not in fields:
1542 fields.append('date')
1543 if fields and 'duration' not in fields:
1544 fields.append('duration')
1547 for base_calendar_id, real_id in select:
1548 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1549 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1553 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1554 # if it's a recurring event, we get the dates from the virtual id (string part):
1555 if not isinstance(ls, (str, int, long)) and len(ls) >= 2 and res.get('recurrency'):
1557 res['date_deadline'] = ls[2]
1558 res['id'] = base_calendar_id
1561 if isinstance(ids, (str, int, long)):
1562 return result and result[0] or False
1565 def copy(self, cr, uid, id, default=None, context=None):
1567 Duplicate record on specified id.
1568 @param self: the object pointer.
1569 @param cr: the current row, from the database cursor,
1570 @param id: id of record from which we duplicated.
1571 @param context: A standard dictionary for contextual values
1572 @return: Duplicate record id.
1576 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1577 alarm_obj = self.pool.get('res.alarm')
1578 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1582 def unlink(self, cr, uid, ids, context=None):
1584 Deletes records specified in ids.
1585 @param self: the object pointer.
1586 @param cr: the current row, from the database cursor,
1587 @param id: List of calendar event's id.
1588 @param context: A standard dictionary for contextual values
1592 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1593 event_id = event_datas['id']
1595 if self.get_edit_all(cr, uid, event_id, vals=None):
1596 event_id = base_calendar_id2real_id(event_id)
1598 if isinstance(event_id, (int, long)):
1599 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1600 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1601 self.unlink_events(cr, uid, [event_id], context=context)
1603 str_event, date_new = event_id.split('-')
1604 event_id = int(str_event)
1605 if event_datas['rrule']:
1606 # Remove one of the recurrent event
1607 date_new = time.strftime("%Y%m%dT%H%M%S", \
1608 time.strptime(date_new, "%Y%m%d%H%M%S"))
1609 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1610 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1612 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1613 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1614 self.unlink_events(cr, uid, [event_id], context=context)
1617 def create(self, cr, uid, vals, context=None):
1620 @param self: the object pointer
1621 @param cr: the current row, from the database cursor,
1622 @param uid: the current user’s ID for security checks,
1623 @param vals: dictionary of every field value.
1624 @param context: A standard dictionary for contextual values
1625 @return: new created record id.
1630 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1631 vals['vtimezone'] = vals['vtimezone'][40:]
1633 updated_vals = self.onchange_dates(cr, uid, [],
1634 vals.get('date', False),
1635 vals.get('duration', False),
1636 vals.get('date_deadline', False),
1637 vals.get('allday', False),
1639 vals.update(updated_vals.get('value', {}))
1641 res = super(calendar_event, self).create(cr, uid, vals, context)
1642 alarm_obj = self.pool.get('res.alarm')
1643 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1646 def do_tentative(self, cr, uid, ids, context=None, *args):
1647 """ Makes event invitation as Tentative
1648 @param self: The object pointer
1649 @param cr: the current row, from the database cursor,
1650 @param uid: the current user’s ID for security checks,
1651 @param ids: List of Event IDs
1652 @param *args: Get Tupple value
1653 @param context: A standard dictionary for contextual values
1655 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1657 def do_cancel(self, cr, uid, ids, context=None, *args):
1658 """ Makes event invitation as Tentative
1659 @param self: The object pointer
1660 @param cr: the current row, from the database cursor,
1661 @param uid: the current user’s ID for security checks,
1662 @param ids: List of Event IDs
1663 @param *args: Get Tupple value
1664 @param context: A standard dictionary for contextual values
1666 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1668 def do_confirm(self, cr, uid, ids, context=None, *args):
1669 """ Makes event invitation as Tentative
1670 @param self: The object pointer
1671 @param cr: the current row, from the database cursor,
1672 @param uid: the current user’s ID for security checks,
1673 @param ids: List of Event IDs
1674 @param *args: Get Tupple value
1675 @param context: A standard dictionary for contextual values
1677 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1681 class calendar_todo(osv.osv):
1682 """ Calendar Task """
1684 _name = "calendar.todo"
1685 _inherit = "calendar.event"
1686 _description = "Calendar Task"
1688 def _get_date(self, cr, uid, ids, name, arg, context=None):
1691 @param self: The object pointer
1692 @param cr: the current row, from the database cursor,
1693 @param uid: the current user’s ID for security checks,
1694 @param ids: List of calendar todo's IDs.
1695 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1696 @param context: A standard dictionary for contextual values
1700 for event in self.browse(cr, uid, ids, context=context):
1701 res[event.id] = event.date_start
1704 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1707 @param self: The object pointer
1708 @param cr: the current row, from the database cursor,
1709 @param uid: the current user’s ID for security checks,
1710 @param id: calendar's ID.
1711 @param value: Get Value
1712 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1713 @param context: A standard dictionary for contextual values
1716 assert name == 'date'
1717 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1720 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1721 string='Duration', store=True, type='datetime'),
1722 'duration': fields.integer('Duration'),
1730 class ir_attachment(osv.osv):
1731 _name = 'ir.attachment'
1732 _inherit = 'ir.attachment'
1734 def search_count(self, cr, user, args, context=None):
1736 @param self: The object pointer
1737 @param cr: the current row, from the database cursor,
1738 @param user: the current user’s ID for security checks,
1739 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1740 @param context: A standard dictionary for contextual values
1745 args1.append(map(lambda x:str(x).split('-')[0], arg))
1746 return super(ir_attachment, self).search_count(cr, user, args1, context)
1750 def create(self, cr, uid, vals, context=None):
1752 id = context.get('default_res_id', False)
1753 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1754 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1756 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1757 context=None, count=False):
1759 @param self: The object pointer
1760 @param cr: the current row, from the database cursor,
1761 @param uid: the current user’s ID for security checks,
1762 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1763 @param offset: The Number of Results to pass,
1764 @param limit: The Number of Results to Return,
1765 @param context: A standard dictionary for contextual values
1769 for i, arg in enumerate(new_args):
1770 if arg[0] == 'res_id':
1771 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1773 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1774 limit=limit, order=order, context=context, count=False)
1777 class ir_values(osv.osv):
1778 _inherit = 'ir.values'
1780 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1781 isobject=False, meta=False, preserve_user=False, company=False):
1784 @param self: The object pointer
1785 @param cr: the current row, from the database cursor,
1786 @param uid: the current user’s ID for security checks,
1787 @param model: Get The Model
1792 if type(data) in (list, tuple):
1793 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1795 new_model.append(data)
1796 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1797 value, replace, isobject, meta, preserve_user, company)
1799 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1800 res_id_req=False, without_user=True, key2_req=True):
1803 @param self: The object pointer
1804 @param cr: the current row, from the database cursor,
1805 @param uid: the current user’s ID for security checks,
1806 @param model: Get The Model
1812 if type(data) in (list, tuple):
1813 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1815 new_model.append(data)
1816 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1817 meta, context, res_id_req, without_user, key2_req)
1821 class ir_model(osv.osv):
1823 _inherit = 'ir.model'
1825 def read(self, cr, uid, ids, fields=None, context=None,
1826 load='_classic_read'):
1828 Overrides orm read method.
1829 @param self: The object pointer
1830 @param cr: the current row, from the database cursor,
1831 @param uid: the current user’s ID for security checks,
1832 @param ids: List of IR Model’s IDs.
1833 @param context: A standard dictionary for contextual values
1835 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1838 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1839 context=context, load=load)
1842 val['id'] = base_calendar_id2real_id(val['id'])
1843 return isinstance(ids, (str, int, long)) and data[0] or data
1847 class virtual_report_spool(web_services.report_spool):
1849 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1852 @param self: The object pointer
1853 @param db: get the current database,
1854 @param uid: the current user’s ID for security checks,
1855 @param context: A standard dictionary for contextual values
1858 if object == 'printscreen.list':
1859 return super(virtual_report_spool, self).exp_report(db, uid, \
1860 object, ids, datas, context)
1863 new_ids.append(base_calendar_id2real_id(id))
1864 if datas.get('id', False):
1865 datas['id'] = base_calendar_id2real_id(datas['id'])
1866 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1868 virtual_report_spool()
1870 class res_users(osv.osv):
1871 _inherit = 'res.users'
1873 def _get_user_avail(self, cr, uid, ids, context=None):
1875 Get User Availability
1876 @param self: The object pointer
1877 @param cr: the current row, from the database cursor,
1878 @param uid: the current user’s ID for security checks,
1879 @param ids: List of res user’s IDs.
1880 @param context: A standard dictionary for contextual values
1883 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1885 attendee_obj = self.pool.get('calendar.attendee')
1886 attendee_ids = attendee_obj.search(cr, uid, [
1887 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1888 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1891 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1892 user_id = attendee_data['user_id']
1894 res.update({user_id:status})
1896 #TOCHECK: Delegated Event
1898 if user_id not in res:
1899 res[user_id] = 'free'
1903 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1905 Get User Availability Function
1906 @param self: The object pointer
1907 @param cr: the current row, from the database cursor,
1908 @param uid: the current user’s ID for security checks,
1909 @param ids: List of res user’s IDs.
1910 @param context: A standard dictionary for contextual values
1913 return self._get_user_avail(cr, uid, ids, context=context)
1916 'availability': fields.function(_get_user_avail_fun, type='selection', \
1917 selection=[('free', 'Free'), ('busy', 'Busy')], \
1918 string='Free/Busy', method=True),
1924 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: