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', '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,) )
1264 for data in cr.dictfetchall():
1265 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1266 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1269 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1270 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1271 start_date = event_date
1272 if not data['rrule']:
1273 if start_date and (event_date < start_date):
1275 if until_date and (event_date > until_date):
1277 idval = real_id2base_calendar_id(data['id'], data['date'])
1278 if not data['recurrent_id']:
1279 result.append(idval)
1282 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1283 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1284 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1285 if ls[1] == data['recurrent_id']:
1286 result.append(idval)
1287 recur_dict.append(ex_id)
1289 exdate = data['exdate'] and data['exdate'].split(',') or []
1290 rrule_str = data['rrule']
1292 rrule_until_date = False
1294 for rule in rrule_str.split(';'):
1295 name, value = rule.split('=')
1298 value = parser.parse(value)
1299 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1300 if until_date and until_date >= rrule_until_date:
1301 until_date = rrule_until_date
1303 value = until_date.strftime("%Y%m%d%H%M%S")
1304 new_rule = '%s=%s' % (name, value)
1305 new_rrule_str.append(new_rule)
1306 if not is_until and until_date:
1307 value = until_date.strftime("%Y%m%d%H%M%S")
1309 new_rule = '%s=%s' % (name, value)
1310 new_rrule_str.append(new_rule)
1311 new_rrule_str = ';'.join(new_rrule_str)
1312 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1313 for r_date in rdates:
1314 if start_date and r_date < start_date:
1316 if until_date and r_date > until_date:
1318 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1319 result.append(idval)
1322 ids = list(set(result)-set(recur_dict))
1323 if isinstance(select, (str, int, long)):
1324 return ids and ids[0] or False
1327 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1329 Compute rule string according to value type RECUR of iCalendar from the values given.
1330 @param self: the object pointer
1331 @param cr: the current row, from the database cursor,
1332 @param uid: the current user’s ID for security checks,
1333 @param datas: dictionary of freq and interval value.
1334 @param context: A standard dictionary for contextual values
1335 @return: String value of the format RECUR of iCalendar
1338 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1342 freq=datas.get('rrule_type')
1346 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1348 if freq == 'weekly':
1349 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1351 weekstring = ';BYDAY=' + ','.join(byday)
1353 elif freq == 'monthly':
1354 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1355 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1356 if datas.get('select1')=='day':
1357 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1358 elif datas.get('select1')=='date':
1359 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1362 if datas.get('end_date'):
1363 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1364 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1365 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1367 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1368 + enddate + monthstring + yearstring
1372 def search(self, cr, uid, args, offset=0, limit=100, order=None,
1373 context=None, count=False):
1375 Overrides orm search method.
1376 @param cr: the current row, from the database cursor,
1377 @param user: the current user’s ID for security checks,
1378 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1379 @param offset: The Number of Results to Pass
1380 @param limit: The Number of Results to Return
1381 @param context: A standard dictionary for contextual values
1382 @param count: If its True the method returns number of records instead of ids
1385 args_without_date = []
1390 if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1391 args_without_date.append(arg)
1393 if arg[1] in ('>', '>='):
1397 elif arg[1] in ('<', '<='):
1401 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1402 offset, limit, order, context, count)
1404 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
1408 def get_edit_all(self, cr, uid, id, vals=None):
1410 return true if we have to edit all meeting from the same recurrent
1411 or only on occurency
1413 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1414 if(vals and 'edit_all' in vals): #we jsut check edit_all
1415 return vals['edit_all']
1416 else: #it's a recurrent event and edit_all is already check
1417 return meeting['recurrency'] and meeting['edit_all']
1422 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1424 Overrides orm write method.
1425 @param self: the object pointer
1426 @param cr: the current row, from the database cursor,
1427 @param uid: the current user’s ID for security checks,
1428 @param ids: List of crm meeting's ids
1429 @param vals: Dictionary of field value.
1430 @param context: A standard dictionary for contextual values
1435 if isinstance(ids, (str, int, long)):
1441 for event_id in select:
1442 real_event_id = base_calendar_id2real_id(event_id)
1445 if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1446 event_id = real_event_id
1449 if len(str(event_id).split('-')) > 1:
1450 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1451 'rrule', 'duration', 'exdate'])
1452 if data.get('rrule'):
1455 'recurrent_uid': real_event_id,
1456 'recurrent_id': data.get('date'),
1457 'rrule_type': 'none',
1460 'recurrency' : False,
1463 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1465 date_new = event_id.split('-')[1]
1466 date_new = time.strftime("%Y%m%dT%H%M%S", \
1467 time.strptime(date_new, "%Y%m%d%H%M%S"))
1468 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1469 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1471 context.update({'active_id': new_id, 'active_ids': [new_id]})
1473 if not real_event_id in new_ids:
1474 new_ids.append(real_event_id)
1476 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1477 vals['vtimezone'] = vals['vtimezone'][40:]
1479 updated_vals = self.onchange_dates(cr, uid, new_ids,
1480 vals.get('date', False),
1481 vals.get('duration', False),
1482 vals.get('date_deadline', False),
1483 vals.get('allday', False),
1485 vals.update(updated_vals.get('value', {}))
1487 if not 'edit_all' in vals:
1488 vals['edit_all'] = False
1491 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1493 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1494 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1495 # change alarm details
1496 alarm_obj = self.pool.get('res.alarm')
1497 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1500 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1502 Overrides orm browse method.
1503 @param self: the object pointer
1504 @param cr: the current row, from the database cursor,
1505 @param uid: the current user’s ID for security checks,
1506 @param ids: List of crm meeting's ids
1507 @param context: A standard dictionary for contextual values
1508 @return: the object list.
1510 if isinstance(ids, (str, int, long)):
1514 select = map(lambda x: base_calendar_id2real_id(x), select)
1515 res = super(calendar_event, self).browse(cr, uid, select, context, \
1516 list_class, fields_process)
1517 if isinstance(ids, (str, int, long)):
1518 return res and res[0] or False
1522 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1524 Overrides orm Read method.Read List of fields for calendar event.
1525 @param cr: the current row, from the database cursor,
1526 @param user: the current user’s ID for security checks,
1527 @param ids: List of calendar event's id.
1528 @param fields: List of fields.
1529 @param context: A standard dictionary for contextual values
1530 @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1532 # FIXME This whole id mangling has to go!
1536 if isinstance(ids, (str, int, long)):
1540 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1542 if fields and 'date' not in fields:
1543 fields.append('date')
1544 if fields and 'duration' not in fields:
1545 fields.append('duration')
1548 for base_calendar_id, real_id in select:
1549 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1550 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 not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1556 res['date_deadline'] = ls[2]
1557 res['id'] = base_calendar_id
1560 if isinstance(ids, (str, int, long)):
1561 return result and result[0] or False
1564 def copy(self, cr, uid, id, default=None, context=None):
1566 Duplicate record on specified id.
1567 @param self: the object pointer.
1568 @param cr: the current row, from the database cursor,
1569 @param id: id of record from which we duplicated.
1570 @param context: A standard dictionary for contextual values
1571 @return: Duplicate record id.
1575 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1576 alarm_obj = self.pool.get('res.alarm')
1577 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1581 def unlink(self, cr, uid, ids, context=None):
1583 Deletes records specified in ids.
1584 @param self: the object pointer.
1585 @param cr: the current row, from the database cursor,
1586 @param id: List of calendar event's id.
1587 @param context: A standard dictionary for contextual values
1591 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1592 event_id = event_datas['id']
1594 if self.get_edit_all(cr, uid, event_id, vals=None):
1595 event_id = base_calendar_id2real_id(event_id)
1597 if isinstance(event_id, (int, long)):
1598 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1599 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1600 self.unlink_events(cr, uid, [event_id], context=context)
1602 str_event, date_new = event_id.split('-')
1603 event_id = int(str_event)
1604 if event_datas['rrule']:
1605 # Remove one of the recurrent event
1606 date_new = time.strftime("%Y%m%dT%H%M%S", \
1607 time.strptime(date_new, "%Y%m%d%H%M%S"))
1608 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1609 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1611 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1612 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1613 self.unlink_events(cr, uid, [event_id], context=context)
1616 def create(self, cr, uid, vals, context=None):
1619 @param self: the object pointer
1620 @param cr: the current row, from the database cursor,
1621 @param uid: the current user’s ID for security checks,
1622 @param vals: dictionary of every field value.
1623 @param context: A standard dictionary for contextual values
1624 @return: new created record id.
1629 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1630 vals['vtimezone'] = vals['vtimezone'][40:]
1632 updated_vals = self.onchange_dates(cr, uid, [],
1633 vals.get('date', False),
1634 vals.get('duration', False),
1635 vals.get('date_deadline', False),
1636 vals.get('allday', False),
1638 vals.update(updated_vals.get('value', {}))
1640 res = super(calendar_event, self).create(cr, uid, vals, context)
1641 alarm_obj = self.pool.get('res.alarm')
1642 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1645 def do_tentative(self, cr, uid, ids, context=None, *args):
1646 """ Makes event invitation as Tentative
1647 @param self: The object pointer
1648 @param cr: the current row, from the database cursor,
1649 @param uid: the current user’s ID for security checks,
1650 @param ids: List of Event IDs
1651 @param *args: Get Tupple value
1652 @param context: A standard dictionary for contextual values
1654 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1656 def do_cancel(self, cr, uid, ids, context=None, *args):
1657 """ Makes event invitation as Tentative
1658 @param self: The object pointer
1659 @param cr: the current row, from the database cursor,
1660 @param uid: the current user’s ID for security checks,
1661 @param ids: List of Event IDs
1662 @param *args: Get Tupple value
1663 @param context: A standard dictionary for contextual values
1665 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1667 def do_confirm(self, cr, uid, ids, context=None, *args):
1668 """ Makes event invitation as Tentative
1669 @param self: The object pointer
1670 @param cr: the current row, from the database cursor,
1671 @param uid: the current user’s ID for security checks,
1672 @param ids: List of Event IDs
1673 @param *args: Get Tupple value
1674 @param context: A standard dictionary for contextual values
1676 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1680 class calendar_todo(osv.osv):
1681 """ Calendar Task """
1683 _name = "calendar.todo"
1684 _inherit = "calendar.event"
1685 _description = "Calendar Task"
1687 def _get_date(self, cr, uid, ids, name, arg, context=None):
1690 @param self: The object pointer
1691 @param cr: the current row, from the database cursor,
1692 @param uid: the current user’s ID for security checks,
1693 @param ids: List of calendar todo's IDs.
1694 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1695 @param context: A standard dictionary for contextual values
1699 for event in self.browse(cr, uid, ids, context=context):
1700 res[event.id] = event.date_start
1703 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1706 @param self: The object pointer
1707 @param cr: the current row, from the database cursor,
1708 @param uid: the current user’s ID for security checks,
1709 @param id: calendar's ID.
1710 @param value: Get Value
1711 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1712 @param context: A standard dictionary for contextual values
1715 assert name == 'date'
1716 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1719 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1720 string='Duration', store=True, type='datetime'),
1721 'duration': fields.integer('Duration'),
1729 class ir_attachment(osv.osv):
1730 _name = 'ir.attachment'
1731 _inherit = 'ir.attachment'
1733 def search_count(self, cr, user, args, context=None):
1735 @param self: The object pointer
1736 @param cr: the current row, from the database cursor,
1737 @param user: the current user’s ID for security checks,
1738 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1739 @param context: A standard dictionary for contextual values
1744 args1.append(map(lambda x:str(x).split('-')[0], arg))
1745 return super(ir_attachment, self).search_count(cr, user, args1, context)
1749 def create(self, cr, uid, vals, context=None):
1751 id = context.get('default_res_id', False)
1752 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1753 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1755 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1756 context=None, count=False):
1758 @param self: The object pointer
1759 @param cr: the current row, from the database cursor,
1760 @param uid: the current user’s ID for security checks,
1761 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1762 @param offset: The Number of Results to pass,
1763 @param limit: The Number of Results to Return,
1764 @param context: A standard dictionary for contextual values
1768 for i, arg in enumerate(new_args):
1769 if arg[0] == 'res_id':
1770 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1772 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1773 limit=limit, order=order, context=context, count=False)
1776 class ir_values(osv.osv):
1777 _inherit = 'ir.values'
1779 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1780 isobject=False, meta=False, preserve_user=False, company=False):
1783 @param self: The object pointer
1784 @param cr: the current row, from the database cursor,
1785 @param uid: the current user’s ID for security checks,
1786 @param model: Get The Model
1791 if type(data) in (list, tuple):
1792 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1794 new_model.append(data)
1795 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1796 value, replace, isobject, meta, preserve_user, company)
1798 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1799 res_id_req=False, without_user=True, key2_req=True):
1802 @param self: The object pointer
1803 @param cr: the current row, from the database cursor,
1804 @param uid: the current user’s ID for security checks,
1805 @param model: Get The Model
1811 if type(data) in (list, tuple):
1812 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1814 new_model.append(data)
1815 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1816 meta, context, res_id_req, without_user, key2_req)
1820 class ir_model(osv.osv):
1822 _inherit = 'ir.model'
1824 def read(self, cr, uid, ids, fields=None, context=None,
1825 load='_classic_read'):
1827 Overrides orm read method.
1828 @param self: The object pointer
1829 @param cr: the current row, from the database cursor,
1830 @param uid: the current user’s ID for security checks,
1831 @param ids: List of IR Model’s IDs.
1832 @param context: A standard dictionary for contextual values
1834 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1837 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1838 context=context, load=load)
1841 val['id'] = base_calendar_id2real_id(val['id'])
1842 return isinstance(ids, (str, int, long)) and data[0] or data
1846 class virtual_report_spool(web_services.report_spool):
1848 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1851 @param self: The object pointer
1852 @param db: get the current database,
1853 @param uid: the current user’s ID for security checks,
1854 @param context: A standard dictionary for contextual values
1857 if object == 'printscreen.list':
1858 return super(virtual_report_spool, self).exp_report(db, uid, \
1859 object, ids, datas, context)
1862 new_ids.append(base_calendar_id2real_id(id))
1863 if datas.get('id', False):
1864 datas['id'] = base_calendar_id2real_id(datas['id'])
1865 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1867 virtual_report_spool()
1869 class res_users(osv.osv):
1870 _inherit = 'res.users'
1872 def _get_user_avail(self, cr, uid, ids, context=None):
1874 Get User Availability
1875 @param self: The object pointer
1876 @param cr: the current row, from the database cursor,
1877 @param uid: the current user’s ID for security checks,
1878 @param ids: List of res user’s IDs.
1879 @param context: A standard dictionary for contextual values
1882 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1884 attendee_obj = self.pool.get('calendar.attendee')
1885 attendee_ids = attendee_obj.search(cr, uid, [
1886 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1887 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1890 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1891 user_id = attendee_data['user_id']
1893 res.update({user_id:status})
1895 #TOCHECK: Delegated Event
1897 if user_id not in res:
1898 res[user_id] = 'free'
1902 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1904 Get User Availability Function
1905 @param self: The object pointer
1906 @param cr: the current row, from the database cursor,
1907 @param uid: the current user’s ID for security checks,
1908 @param ids: List of res user’s IDs.
1909 @param context: A standard dictionary for contextual values
1912 return self._get_user_avail(cr, uid, ids, context=context)
1915 'availability': fields.function(_get_user_avail_fun, type='selection', \
1916 selection=[('free', 'Free'), ('busy', 'Busy')], \
1917 string='Free/Busy', method=True),
1923 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: