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 email_message_obj = self.pool.get('email.message')
484 for att in self.browse(cr, uid, ids, context=context):
485 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
486 sign = '<br>'.join(sign and sign.split('\n') or [])
491 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
493 for att2 in self.browse(cr, uid, other_invitation_ids):
494 att_infos.append(((att2.user_id and att2.user_id.name) or \
495 (att2.partner_id and att2.partner_id.name) or \
496 att2.email) + ' - Status: ' + att2.state.title())
497 body_vals = {'name': res_obj.name,
498 'start_date': res_obj.date,
499 'end_date': res_obj.date_deadline or False,
500 'description': res_obj.description or '-',
501 'location': res_obj.location or '-',
502 'attendees': '<br>'.join(att_infos),
503 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
507 body = html_invitation % body_vals
508 if mail_to and email_from:
509 attach = self.get_ics_file(cr, uid, res_obj, context=context)
510 email_message_obj.email_send(cr, uid,
515 model='calendar.attendee',
516 attach=attach and [('invitation.ics', attach)] or None,
522 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
524 Make entry on email and availbility on change of user_id field.
525 @param cr: the current row, from the database cursor,
526 @param uid: the current user’s ID for security checks,
527 @param ids: List of calendar attendee’s IDs.
528 @param user_id: Changed value of User id
529 @return: dictionary of value. which put value in email and availability fields.
533 return {'value': {'email': ''}}
534 usr_obj = self.pool.get('res.users')
535 user = usr_obj.browse(cr, uid, user_id, *args)
536 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
538 def do_tentative(self, cr, uid, ids, context=None, *args):
539 """ Makes event invitation as Tentative
540 @param self: The object pointer
541 @param cr: the current row, from the database cursor,
542 @param uid: the current user’s ID for security checks,
543 @param ids: List of calendar attendee’s IDs
544 @param *args: Get Tupple value
545 @param context: A standard dictionary for contextual values
547 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
549 def do_accept(self, cr, uid, ids, context=None, *args):
551 Update state of invitation as Accepted and
552 if the invited user is other then event user it will make a copy of this event for invited user
553 @param cr: the current row, from the database cursor,
554 @param uid: the current user’s ID for security checks,
555 @param ids: List of calendar attendee’s IDs.
556 @param context: A standard dictionary for contextual values
562 for vals in self.browse(cr, uid, ids, context=context):
563 if vals.ref and vals.ref.user_id:
564 mod_obj = self.pool.get(vals.ref._name)
565 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
566 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
567 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
571 def do_decline(self, cr, uid, ids, context=None, *args):
572 """ Marks event invitation as Declined
573 @param self: The object pointer
574 @param cr: the current row, from the database cursor,
575 @param uid: the current user’s ID for security checks,
576 @param ids: List of calendar attendee’s IDs
577 @param *args: Get Tupple value
578 @param context: A standard dictionary for contextual values """
581 return self.write(cr, uid, ids, {'state': 'declined'}, context)
583 def create(self, cr, uid, vals, context=None):
584 """ Overrides orm create method.
585 @param self: The object pointer
586 @param cr: the current row, from the database cursor,
587 @param uid: the current user’s ID for security checks,
588 @param vals: Get Values
589 @param context: A standard dictionary for contextual values """
593 if not vals.get("email") and vals.get("cn"):
594 cnval = vals.get("cn").split(':')
595 email = filter(lambda x:x.__contains__('@'), cnval)
596 vals['email'] = email and email[0] or ''
597 vals['cn'] = vals.get("cn")
598 res = super(calendar_attendee, self).create(cr, uid, vals, context)
602 class res_alarm(osv.osv):
603 """Resource Alarm """
605 _description = 'Basic Alarm Information'
608 'name':fields.char('Name', size=256, required=True),
609 'trigger_occurs': fields.selection([('before', 'Before'), \
610 ('after', 'After')], \
611 'Triggers', required=True),
612 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
613 ('hours', 'Hours'), \
614 ('days', 'Days')], 'Interval', \
616 'trigger_duration': fields.integer('Duration', required=True),
617 'trigger_related': fields.selection([('start', 'The event starts'), \
618 ('end', 'The event ends')], \
619 'Related to', required=True),
620 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
621 are both optional, but if one occurs, so MUST the other"""),
622 'repeat': fields.integer('Repeat'),
623 'active': fields.boolean('Active', help="If the active field is set to \
624 true, it will allow you to hide the event alarm information without removing it.")
627 'trigger_interval': 'minutes',
628 'trigger_duration': 5,
629 'trigger_occurs': 'before',
630 'trigger_related': 'start',
634 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
636 Create Alarm for event.
637 @param cr: the current row, from the database cursor,
638 @param uid: the current user’s ID for security checks,
639 @param ids: List of res alarm’s IDs.
640 @param model: Model name.
641 @param date: Event date
642 @param context: A standard dictionary for contextual values
647 alarm_obj = self.pool.get('calendar.alarm')
648 res_alarm_obj = self.pool.get('res.alarm')
649 ir_obj = self.pool.get('ir.model')
650 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
652 model_obj = self.pool.get(model)
653 for data in model_obj.browse(cr, uid, ids, context=context):
655 basic_alarm = data.alarm_id
656 cal_alarm = data.base_calendar_alarm_id
657 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
659 # Find for existing res.alarm
660 duration = cal_alarm.trigger_duration
661 interval = cal_alarm.trigger_interval
662 occurs = cal_alarm.trigger_occurs
663 related = cal_alarm.trigger_related
664 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
665 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
668 'trigger_duration': duration,
669 'trigger_interval': interval,
670 'trigger_occurs': occurs,
671 'trigger_related': related,
672 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
674 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
676 new_res_alarm = alarm_ids[0]
677 cr.execute('UPDATE %s ' % model_obj._table + \
678 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
680 (cal_alarm.id, new_res_alarm, data.id))
682 self.do_alarm_unlink(cr, uid, [data.id], model)
686 'description': data.description,
688 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
689 'trigger_related': basic_alarm.trigger_related,
690 'trigger_duration': basic_alarm.trigger_duration,
691 'trigger_occurs': basic_alarm.trigger_occurs,
692 'trigger_interval': basic_alarm.trigger_interval,
693 'duration': basic_alarm.duration,
694 'repeat': basic_alarm.repeat,
696 'event_date': data[date],
698 'model_id': model_id,
701 alarm_id = alarm_obj.create(cr, uid, vals)
702 cr.execute('UPDATE %s ' % model_obj._table + \
703 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
705 ( alarm_id, basic_alarm.id, data.id) )
708 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
710 Delete alarm specified in ids
711 @param cr: the current row, from the database cursor,
712 @param uid: the current user’s ID for security checks,
713 @param ids: List of res alarm’s IDs.
714 @param model: Model name for which alarm is to be cleared.
719 alarm_obj = self.pool.get('calendar.alarm')
720 ir_obj = self.pool.get('ir.model')
721 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
722 model_obj = self.pool.get(model)
723 for datas in model_obj.browse(cr, uid, ids, context=context):
724 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
726 alarm_obj.unlink(cr, uid, alarm_ids)
727 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
728 where id=%%s' % model_obj._table,(datas.id,))
733 class calendar_alarm(osv.osv):
734 _name = 'calendar.alarm'
735 _description = 'Event alarm information'
736 _inherit = 'res.alarm'
740 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
741 'name': fields.char('Summary', size=124, help="""Contains the text to be \
742 used as the message subject for email \
743 or contains the text to be used for display"""),
744 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
745 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
746 required=True, help="Defines the action to be invoked when an alarm is triggered"),
747 'description': fields.text('Description', help='Provides a more complete \
748 description of the calendar component, than that \
749 provided by the "SUMMARY" property'),
750 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
751 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
752 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
753 which is rendered when the alarm is triggered for audio,
754 * File which is intended to be sent as message attachments for email,
755 * Points to a procedure resource, which is invoked when\
756 the alarm is triggered for procedure."""),
757 'res_id': fields.integer('Resource ID'),
758 'model_id': fields.many2one('ir.model', 'Model'),
759 'user_id': fields.many2one('res.users', 'Owner'),
760 'event_date': fields.datetime('Event Date'),
761 'event_end_date': fields.datetime('Event End Date'),
762 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
763 'state':fields.selection([
768 ], 'State', select=True, readonly=True),
776 def create(self, cr, uid, vals, context=None):
778 Overrides orm create method.
779 @param self: The object pointer
780 @param cr: the current row, from the database cursor,
781 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
782 @param context: A standard dictionary for contextual values
783 @return: new record id for calendar_alarm.
787 event_date = vals.get('event_date', False)
789 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
790 if vals['trigger_interval'] == 'days':
791 delta = timedelta(days=vals['trigger_duration'])
792 if vals['trigger_interval'] == 'hours':
793 delta = timedelta(hours=vals['trigger_duration'])
794 if vals['trigger_interval'] == 'minutes':
795 delta = timedelta(minutes=vals['trigger_duration'])
796 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
797 vals['trigger_date'] = trigger_date
798 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
801 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
803 """Scheduler for event reminder
804 @param self: The object pointer
805 @param cr: the current row, from the database cursor,
806 @param uid: the current user’s ID for security checks,
807 @param ids: List of calendar alarm’s IDs.
808 @param use_new_cursor: False or the dbname
809 @param context: A standard dictionary for contextual values
811 return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
814 email_message_obj = self.pool.get('email.message')
815 current_datetime = datetime.now()
816 request_obj = self.pool.get('res.request')
817 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
821 for alarm in self.browse(cr, uid, alarm_ids, context=context):
822 next_trigger_date = None
824 model_obj = self.pool.get(alarm.model_id.model)
825 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
829 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
830 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
832 trigger_interval = alarm.trigger_interval
833 if trigger_interval == 'days':
834 delta = timedelta(days=alarm.trigger_duration)
835 if trigger_interval == 'hours':
836 delta = timedelta(hours=alarm.trigger_duration)
837 if trigger_interval == 'minutes':
838 delta = timedelta(minutes=alarm.trigger_duration)
839 delta = alarm.trigger_occurs == 'after' and delta or -delta
841 for rdate in recurrent_dates:
842 if rdate + delta > current_datetime:
844 if rdate + delta <= current_datetime:
845 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
846 rest_dates = recurrent_dates[len(re_dates):]
847 next_trigger_date = rest_dates and rest_dates[0] or None
850 re_dates = [alarm.trigger_date]
852 for r_date in re_dates:
853 ref = alarm.model_id.model + ',' + str(alarm.res_id)
855 # search for alreay sent requests
856 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
859 if alarm.action == 'display':
862 'act_from': alarm.user_id.id,
863 'act_to': alarm.user_id.id,
864 'body': alarm.description,
865 'trigger_date': r_date,
868 request_id = request_obj.create(cr, uid, value)
869 request_ids = [request_id]
870 for attendee in res_obj.attendee_ids:
872 value['act_to'] = attendee.user_id.id
873 request_id = request_obj.create(cr, uid, value)
874 request_ids.append(request_id)
875 request_obj.request_send(cr, uid, request_ids)
877 if alarm.action == 'email':
878 sub = '[Openobject Reminder] %s' % (alarm.name)
890 """ % (alarm.name, alarm.trigger_date, alarm.description, \
891 alarm.user_id.name, alarm.user_id.signature)
892 mail_to = [alarm.user_id.address_id.email]
893 for att in alarm.attendee_ids:
894 mail_to.append(att.user_id.address_id.email)
896 email_message_obj.email_send(cr, uid,
897 tools.config.get('email_from', False),
901 model='calendar.alarm'
903 if next_trigger_date:
904 update_vals.update({'trigger_date': next_trigger_date})
906 update_vals.update({'state': 'done'})
907 self.write(cr, uid, [alarm.id], update_vals)
913 class calendar_event(osv.osv):
914 _name = "calendar.event"
915 _description = "Calendar Event"
918 def _tz_get(self, cr, uid, context=None):
919 return [(x.lower(), x) for x in pytz.all_timezones]
921 def onchange_allday(self, cr, uid, ids, allday, context=None):
922 """Sets duration as 24 Hours if event is selected for all day
923 @param self: The object pointer
924 @param cr: the current row, from the database cursor,
925 @param uid: the current user’s ID for security checks,
926 @param ids: List of calendar event’s IDs.
927 @param allday: Value of allday boolean
928 @param context: A standard dictionary for contextual values
930 if not allday or not ids:
935 return {'value': value}
937 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
938 """Returns duration and/or end date based on values passed
939 @param self: The object pointer
940 @param cr: the current row, from the database cursor,
941 @param uid: the current user’s ID for security checks,
942 @param ids: List of calendar event’s IDs.
943 @param start_date: Starting date
944 @param duration: Duration between start date and end date
945 @param end_date: Ending Datee
946 @param context: A standard dictionary for contextual values
954 if not end_date and not duration:
956 value['duration'] = duration
958 if allday: # For all day event
959 value = {'duration': 24}
962 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
963 if end_date and not duration:
964 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
966 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
967 value['duration'] = round(duration, 2)
969 end = start + timedelta(hours=duration)
970 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
971 elif end_date and duration and not allday:
972 # we have both, keep them synchronized:
973 # set duration based on end_date (arbitrary decision: this avoid
974 # getting dates like 06:31:48 instead of 06:32:00)
975 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
977 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
978 value['duration'] = round(duration, 2)
980 return {'value': value}
982 def unlink_events(self, cr, uid, ids, context=None):
984 This function deletes event which are linked with the event with recurrent_uid
985 (Removes the events which refers to the same UID value)
990 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
991 r_ids = map(lambda x: x[0], cr.fetchall())
992 self.unlink(cr, uid, r_ids, context=context)
995 def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
997 Sets values of fields that defines event recurrence from the value of rrule string
998 @param self: The object pointer
999 @param cr: the current row, from the database cursor,
1000 @param id: List of calendar event's ids.
1001 @param context: A standard dictionary for contextual values
1002 @return: dictionary of rrule value.
1006 cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
1007 mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
1008 day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
1011 cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1014 for part in value.split(';'):
1015 if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1016 rrule_type = part.lower()[5:]
1019 rrule_type = 'custom'
1021 ans = value.split(';')
1023 val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1024 if not val.get('interval'):
1025 rrule_type = 'custom'
1026 elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1027 rrule_type = 'custom'
1029 qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1030 qry_args = [ rrule_type, ]
1031 new_val = val.copy()
1032 for k, v in val.items():
1033 if val['freq'] == 'weekly' and val.get('byday'):
1034 for day in val['byday'].split(','):
1038 if val.get('until'):
1039 until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
1040 new_val['end_date'] = until.strftime('%Y-%m-%d')
1042 new_val.pop('until')
1044 if val.get('bymonthday'):
1045 new_val['day'] = val.get('bymonthday')
1046 val.pop('bymonthday')
1047 new_val['select1'] = 'date'
1048 new_val.pop('bymonthday')
1050 if val.get('byday'):
1051 d = val.get('byday')
1053 new_val['byday'] = d[:2]
1054 new_val['week_list'] = d[2:4].upper()
1056 new_val['byday'] = d[:1]
1057 new_val['week_list'] = d[1:3].upper()
1058 new_val['select1'] = 'day'
1060 if val.get('bymonth'):
1061 new_val['month_list'] = val.get('bymonth')
1063 new_val.pop('bymonth')
1065 for k, v in new_val.items():
1066 qry += ", %s=%%s" % k
1069 qry = qry + " where id=%s"
1071 cr.execute(qry, qry_args)
1074 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
1076 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
1077 @param self: The object pointer
1078 @param cr: the current row, from the database cursor,
1079 @param id: List of calendar event's ids.
1080 @param context: A standard dictionary for contextual values
1081 @return: dictionary of rrule value.
1084 for datas in self.read(cr, uid, ids, context=context):
1086 if datas.get('rrule_type'):
1087 if datas.get('rrule_type') == 'none':
1088 result[event] = False
1089 cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
1090 if datas.get('rrule_type') :
1091 if datas.get('interval', 0) < 0:
1092 raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
1093 if datas.get('count', 0) < 0:
1094 raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1095 rrule_custom = self.compute_rule_string(cr, uid, datas, \
1097 result[event] = rrule_custom
1099 result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
1101 for id, myrule in result.items():
1102 #Remove the events generated from recurrent event
1104 self.unlink_events(cr, uid, [id], context=context)
1108 'id': fields.integer('ID'),
1109 'sequence': fields.integer('Sequence'),
1110 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1111 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1112 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1113 'create_date': fields.datetime('Created', readonly=True),
1114 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1115 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1116 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1117 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1118 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1119 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1120 'Show as', states={'done': [('readonly', True)]}),
1121 'base_calendar_url': fields.char('Caldav URL', size=264),
1122 'state': fields.selection([('tentative', 'Tentative'),
1123 ('confirmed', 'Confirmed'),
1124 ('cancelled', 'Cancelled')], 'State', readonly=True),
1125 'exdate': fields.text('Exception Date/Times', help="This property \
1126 defines the list of date/time exceptions for a recurring calendar component."),
1127 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1128 rule or repeating pattern of time to exclude from the recurring rule."),
1129 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1130 string='Recurrent Rule', store=True, \
1131 fnct_inv=_set_rrulestring, help='Defines a\
1132 rule or repeating pattern for recurring events\n\
1133 e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
1134 FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
1135 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1136 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1137 ('yearly', 'Yearly'),],
1138 'Recurrency', states={'done': [('readonly', True)]},
1139 help="Let the event automatically repeat at that interval"),
1140 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1141 help="Set an alarm at this time, before the event occurs" ),
1142 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1143 'recurrent_uid': fields.integer('Recurrent ID'),
1144 'recurrent_id': fields.datetime('Recurrent ID date'),
1145 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1146 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1147 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1148 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1149 'freq': fields.selection([('None', 'No Repeat'),
1150 ('hourly', 'Hours'),
1152 ('weekly', 'Weeks'),
1153 ('monthly', 'Months'),
1154 ('yearly', 'Years'), ], 'Frequency'),
1156 'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1157 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1158 'count': fields.integer('Repeat', help="Repeat x times"),
1159 'mo': fields.boolean('Mon'),
1160 'tu': fields.boolean('Tue'),
1161 'we': fields.boolean('Wed'),
1162 'th': fields.boolean('Thu'),
1163 'fr': fields.boolean('Fri'),
1164 'sa': fields.boolean('Sat'),
1165 'su': fields.boolean('Sun'),
1166 'select1': fields.selection([('date', 'Date of month'),
1167 ('day', 'Day of month')], 'Option'),
1168 'day': fields.integer('Date of month'),
1169 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1170 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1171 ('FR', 'Friday'), ('SA', 'Saturday'), \
1172 ('SU', 'Sunday')], 'Weekday'),
1173 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1174 ('3', 'Third'), ('4', 'Fourth'), \
1175 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1176 'month_list': fields.selection(months.items(), 'Month'),
1177 'end_date': fields.date('Repeat Until'),
1178 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1179 'event_id', 'attendee_id', 'Attendees'),
1180 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1181 'active': fields.boolean('Active', help="If the active field is set to \
1182 true, it will allow you to hide the event alarm information without removing it."),
1183 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1184 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1186 def default_organizer(self, cr, uid, context=None):
1187 user_pool = self.pool.get('res.users')
1188 user = user_pool.browse(cr, uid, uid, context=context)
1191 res += " <%s>" %(user.user_email)
1195 'end_type' : 'forever',
1196 'state': 'tentative',
1203 'user_id': lambda self, cr, uid, ctx: uid,
1204 'organizer': default_organizer,
1208 def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
1213 if edit_all and rrule_type:
1215 base_calendar_id2real_id(id)
1218 def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1220 Modifies the recurring event
1221 @param cr: the current row, from the database cursor,
1222 @param uid: the current user’s ID for security checks,
1223 @param event_ids: List of crm meeting’s IDs.
1224 @param context: A standard dictionary for contextual values
1227 for event_id in event_ids:
1228 event_id = base_calendar_id2real_id(event_id)
1231 defaults.update({'table': self._table})
1233 qry = "UPDATE %(table)s set name = '%(name)s', \
1234 date = '%(date)s', date_deadline = '%(date_deadline)s'"
1235 if defaults.get('alarm_id'):
1236 qry += ", alarm_id = %(alarm_id)s"
1237 if defaults.get('location'):
1238 qry += ", location = '%(location)s'"
1239 qry += "WHERE id = %s" % (event_id)
1240 cr.execute(qry, defaults)
1244 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
1245 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1246 This method gives ids of dates that comes between start date and end date of calendar views
1247 @param self: The object pointer
1248 @param cr: the current row, from the database cursor,
1249 @param uid: the current user’s ID for security checks,
1250 @param base_start_date: Get Start Date
1251 @param base_until_date: Get End Date
1252 @param limit: The Number of Results to Return """
1256 if isinstance(select, (str, int, long)):
1262 if ids and (base_start_date or base_until_date):
1263 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1264 m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1265 " m where m.id = ANY(%s)", (ids,) )
1268 for data in cr.dictfetchall():
1269 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1270 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1273 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1274 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1275 start_date = event_date
1276 if not data['rrule']:
1277 if start_date and (event_date < start_date):
1279 if until_date and (event_date > until_date):
1281 idval = real_id2base_calendar_id(data['id'], data['date'])
1282 if not data['recurrent_id']:
1283 result.append(idval)
1286 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1287 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1288 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1289 if ls[1] == data['recurrent_id']:
1290 result.append(idval)
1291 recur_dict.append(ex_id)
1293 exdate = data['exdate'] and data['exdate'].split(',') or []
1294 rrule_str = data['rrule']
1296 rrule_until_date = False
1298 for rule in rrule_str.split(';'):
1299 name, value = rule.split('=')
1302 value = parser.parse(value)
1303 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1304 if until_date and until_date >= rrule_until_date:
1305 until_date = rrule_until_date
1307 value = until_date.strftime("%Y%m%d%H%M%S")
1308 new_rule = '%s=%s' % (name, value)
1309 new_rrule_str.append(new_rule)
1310 if not is_until and until_date:
1311 value = until_date.strftime("%Y%m%d%H%M%S")
1313 new_rule = '%s=%s' % (name, value)
1314 new_rrule_str.append(new_rule)
1315 new_rrule_str = ';'.join(new_rrule_str)
1316 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1317 for r_date in rdates:
1318 if start_date and r_date < start_date:
1320 if until_date and r_date > until_date:
1322 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1323 result.append(idval)
1326 ids = list(set(result)-set(recur_dict))
1327 if isinstance(select, (str, int, long)):
1328 return ids and ids[0] or False
1331 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1333 Compute rule string according to value type RECUR of iCalendar from the values given.
1334 @param self: the object pointer
1335 @param cr: the current row, from the database cursor,
1336 @param uid: the current user’s ID for security checks,
1337 @param datas: dictionary of freq and interval value.
1338 @param context: A standard dictionary for contextual values
1339 @return: String value of the format RECUR of iCalendar
1342 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1346 freq=datas.get('rrule_type')
1350 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1352 if freq == 'weekly':
1353 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1355 weekstring = ';BYDAY=' + ','.join(byday)
1357 elif freq == 'monthly':
1358 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1359 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1360 if datas.get('select1')=='day':
1361 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1362 elif datas.get('select1')=='date':
1363 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1366 if datas.get('end_date'):
1367 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1368 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1369 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1371 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1372 + enddate + monthstring + yearstring
1376 def search(self, cr, uid, args, offset=0, limit=100, order=None,
1377 context=None, count=False):
1379 Overrides orm search method.
1380 @param cr: the current row, from the database cursor,
1381 @param user: the current user’s ID for security checks,
1382 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1383 @param offset: The Number of Results to Pass
1384 @param limit: The Number of Results to Return
1385 @param context: A standard dictionary for contextual values
1386 @param count: If its True the method returns number of records instead of ids
1389 args_without_date = []
1394 if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1395 args_without_date.append(arg)
1397 if arg[1] in ('>', '>='):
1401 elif arg[1] in ('<', '<='):
1405 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1406 offset, limit, order, context, count)
1408 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
1412 def get_edit_all(self, cr, uid, id, vals=None):
1414 return true if we have to edit all meeting from the same recurrent
1415 or only on occurency
1417 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1418 if(vals and 'edit_all' in vals): #we jsut check edit_all
1419 return vals['edit_all']
1420 else: #it's a recurrent event and edit_all is already check
1421 return meeting['recurrency'] and meeting['edit_all']
1426 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1428 Overrides orm write method.
1429 @param self: the object pointer
1430 @param cr: the current row, from the database cursor,
1431 @param uid: the current user’s ID for security checks,
1432 @param ids: List of crm meeting's ids
1433 @param vals: Dictionary of field value.
1434 @param context: A standard dictionary for contextual values
1439 if isinstance(ids, (str, int, long)):
1445 for event_id in select:
1446 real_event_id = base_calendar_id2real_id(event_id)
1449 if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1450 event_id = real_event_id
1453 if len(str(event_id).split('-')) > 1:
1454 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1455 'rrule', 'duration', 'exdate'])
1456 if data.get('rrule'):
1459 'recurrent_uid': real_event_id,
1460 'recurrent_id': data.get('date'),
1461 'rrule_type': 'none',
1464 'recurrency' : False,
1467 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1469 date_new = event_id.split('-')[1]
1470 date_new = time.strftime("%Y%m%dT%H%M%S", \
1471 time.strptime(date_new, "%Y%m%d%H%M%S"))
1472 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1473 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1475 context.update({'active_id': new_id, 'active_ids': [new_id]})
1477 if not real_event_id in new_ids:
1478 new_ids.append(real_event_id)
1480 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1481 vals['vtimezone'] = vals['vtimezone'][40:]
1483 updated_vals = self.onchange_dates(cr, uid, new_ids,
1484 vals.get('date', False),
1485 vals.get('duration', False),
1486 vals.get('date_deadline', False),
1487 vals.get('allday', False),
1489 vals.update(updated_vals.get('value', {}))
1491 if not 'edit_all' in vals:
1492 vals['edit_all'] = False
1495 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1497 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1498 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1499 # change alarm details
1500 alarm_obj = self.pool.get('res.alarm')
1501 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1504 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1506 Overrides orm browse method.
1507 @param self: the object pointer
1508 @param cr: the current row, from the database cursor,
1509 @param uid: the current user’s ID for security checks,
1510 @param ids: List of crm meeting's ids
1511 @param context: A standard dictionary for contextual values
1512 @return: the object list.
1514 if isinstance(ids, (str, int, long)):
1518 select = map(lambda x: base_calendar_id2real_id(x), select)
1519 res = super(calendar_event, self).browse(cr, uid, select, context, \
1520 list_class, fields_process)
1521 if isinstance(ids, (str, int, long)):
1522 return res and res[0] or False
1526 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1528 Overrides orm Read method.Read List of fields for calendar event.
1529 @param cr: the current row, from the database cursor,
1530 @param user: the current user’s ID for security checks,
1531 @param ids: List of calendar event's id.
1532 @param fields: List of fields.
1533 @param context: A standard dictionary for contextual values
1534 @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1536 # FIXME This whole id mangling has to go!
1540 if isinstance(ids, (str, int, long)):
1544 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1546 if fields and 'date' not in fields:
1547 fields.append('date')
1548 if fields and 'duration' not in fields:
1549 fields.append('duration')
1552 for base_calendar_id, real_id in select:
1553 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1554 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1557 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1558 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1560 res['date_deadline'] = ls[2]
1561 res['id'] = base_calendar_id
1564 if isinstance(ids, (str, int, long)):
1565 return result and result[0] or False
1568 def copy(self, cr, uid, id, default=None, context=None):
1570 Duplicate record on specified id.
1571 @param self: the object pointer.
1572 @param cr: the current row, from the database cursor,
1573 @param id: id of record from which we duplicated.
1574 @param context: A standard dictionary for contextual values
1575 @return: Duplicate record id.
1579 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1580 alarm_obj = self.pool.get('res.alarm')
1581 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1585 def unlink(self, cr, uid, ids, context=None):
1587 Deletes records specified in ids.
1588 @param self: the object pointer.
1589 @param cr: the current row, from the database cursor,
1590 @param id: List of calendar event's id.
1591 @param context: A standard dictionary for contextual values
1595 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1596 event_id = event_datas['id']
1598 if self.get_edit_all(cr, uid, event_id, vals=None):
1599 event_id = base_calendar_id2real_id(event_id)
1601 if isinstance(event_id, (int, long)):
1602 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1603 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1604 self.unlink_events(cr, uid, [event_id], context=context)
1606 str_event, date_new = event_id.split('-')
1607 event_id = int(str_event)
1608 if event_datas['rrule']:
1609 # Remove one of the recurrent event
1610 date_new = time.strftime("%Y%m%dT%H%M%S", \
1611 time.strptime(date_new, "%Y%m%d%H%M%S"))
1612 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1613 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1615 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1616 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1617 self.unlink_events(cr, uid, [event_id], context=context)
1620 def create(self, cr, uid, vals, context=None):
1623 @param self: the object pointer
1624 @param cr: the current row, from the database cursor,
1625 @param uid: the current user’s ID for security checks,
1626 @param vals: dictionary of every field value.
1627 @param context: A standard dictionary for contextual values
1628 @return: new created record id.
1633 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1634 vals['vtimezone'] = vals['vtimezone'][40:]
1636 updated_vals = self.onchange_dates(cr, uid, [],
1637 vals.get('date', False),
1638 vals.get('duration', False),
1639 vals.get('date_deadline', False),
1640 vals.get('allday', False),
1642 vals.update(updated_vals.get('value', {}))
1644 res = super(calendar_event, self).create(cr, uid, vals, context)
1645 alarm_obj = self.pool.get('res.alarm')
1646 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1649 def do_tentative(self, cr, uid, ids, context=None, *args):
1650 """ Makes event invitation as Tentative
1651 @param self: The object pointer
1652 @param cr: the current row, from the database cursor,
1653 @param uid: the current user’s ID for security checks,
1654 @param ids: List of Event IDs
1655 @param *args: Get Tupple value
1656 @param context: A standard dictionary for contextual values
1658 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1660 def do_cancel(self, cr, uid, ids, context=None, *args):
1661 """ Makes event invitation as Tentative
1662 @param self: The object pointer
1663 @param cr: the current row, from the database cursor,
1664 @param uid: the current user’s ID for security checks,
1665 @param ids: List of Event IDs
1666 @param *args: Get Tupple value
1667 @param context: A standard dictionary for contextual values
1669 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1671 def do_confirm(self, cr, uid, ids, context=None, *args):
1672 """ Makes event invitation as Tentative
1673 @param self: The object pointer
1674 @param cr: the current row, from the database cursor,
1675 @param uid: the current user’s ID for security checks,
1676 @param ids: List of Event IDs
1677 @param *args: Get Tupple value
1678 @param context: A standard dictionary for contextual values
1680 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1684 class calendar_todo(osv.osv):
1685 """ Calendar Task """
1687 _name = "calendar.todo"
1688 _inherit = "calendar.event"
1689 _description = "Calendar Task"
1691 def _get_date(self, cr, uid, ids, name, arg, context=None):
1694 @param self: The object pointer
1695 @param cr: the current row, from the database cursor,
1696 @param uid: the current user’s ID for security checks,
1697 @param ids: List of calendar todo's IDs.
1698 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1699 @param context: A standard dictionary for contextual values
1703 for event in self.browse(cr, uid, ids, context=context):
1704 res[event.id] = event.date_start
1707 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1710 @param self: The object pointer
1711 @param cr: the current row, from the database cursor,
1712 @param uid: the current user’s ID for security checks,
1713 @param id: calendar's ID.
1714 @param value: Get Value
1715 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1716 @param context: A standard dictionary for contextual values
1719 assert name == 'date'
1720 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1723 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1724 string='Duration', store=True, type='datetime'),
1725 'duration': fields.integer('Duration'),
1733 class ir_attachment(osv.osv):
1734 _name = 'ir.attachment'
1735 _inherit = 'ir.attachment'
1737 def search_count(self, cr, user, args, context=None):
1739 @param self: The object pointer
1740 @param cr: the current row, from the database cursor,
1741 @param user: the current user’s ID for security checks,
1742 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1743 @param context: A standard dictionary for contextual values
1748 args1.append(map(lambda x:str(x).split('-')[0], arg))
1749 return super(ir_attachment, self).search_count(cr, user, args1, context)
1753 def create(self, cr, uid, vals, context=None):
1755 id = context.get('default_res_id', False)
1756 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1757 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1759 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1760 context=None, count=False):
1762 @param self: The object pointer
1763 @param cr: the current row, from the database cursor,
1764 @param uid: the current user’s ID for security checks,
1765 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1766 @param offset: The Number of Results to pass,
1767 @param limit: The Number of Results to Return,
1768 @param context: A standard dictionary for contextual values
1772 for i, arg in enumerate(new_args):
1773 if arg[0] == 'res_id':
1774 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1776 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1777 limit=limit, order=order, context=context, count=False)
1780 class ir_values(osv.osv):
1781 _inherit = 'ir.values'
1783 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1784 isobject=False, meta=False, preserve_user=False, company=False):
1787 @param self: The object pointer
1788 @param cr: the current row, from the database cursor,
1789 @param uid: the current user’s ID for security checks,
1790 @param model: Get The Model
1795 if type(data) in (list, tuple):
1796 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1798 new_model.append(data)
1799 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1800 value, replace, isobject, meta, preserve_user, company)
1802 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1803 res_id_req=False, without_user=True, key2_req=True):
1806 @param self: The object pointer
1807 @param cr: the current row, from the database cursor,
1808 @param uid: the current user’s ID for security checks,
1809 @param model: Get The Model
1815 if type(data) in (list, tuple):
1816 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1818 new_model.append(data)
1819 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1820 meta, context, res_id_req, without_user, key2_req)
1824 class ir_model(osv.osv):
1826 _inherit = 'ir.model'
1828 def read(self, cr, uid, ids, fields=None, context=None,
1829 load='_classic_read'):
1831 Overrides orm read method.
1832 @param self: The object pointer
1833 @param cr: the current row, from the database cursor,
1834 @param uid: the current user’s ID for security checks,
1835 @param ids: List of IR Model’s IDs.
1836 @param context: A standard dictionary for contextual values
1838 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1841 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1842 context=context, load=load)
1845 val['id'] = base_calendar_id2real_id(val['id'])
1846 return isinstance(ids, (str, int, long)) and data[0] or data
1850 class virtual_report_spool(web_services.report_spool):
1852 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1855 @param self: The object pointer
1856 @param db: get the current database,
1857 @param uid: the current user’s ID for security checks,
1858 @param context: A standard dictionary for contextual values
1861 if object == 'printscreen.list':
1862 return super(virtual_report_spool, self).exp_report(db, uid, \
1863 object, ids, datas, context)
1866 new_ids.append(base_calendar_id2real_id(id))
1867 if datas.get('id', False):
1868 datas['id'] = base_calendar_id2real_id(datas['id'])
1869 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1871 virtual_report_spool()
1873 class res_users(osv.osv):
1874 _inherit = 'res.users'
1876 def _get_user_avail(self, cr, uid, ids, context=None):
1878 Get User Availability
1879 @param self: The object pointer
1880 @param cr: the current row, from the database cursor,
1881 @param uid: the current user’s ID for security checks,
1882 @param ids: List of res user’s IDs.
1883 @param context: A standard dictionary for contextual values
1886 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1888 attendee_obj = self.pool.get('calendar.attendee')
1889 attendee_ids = attendee_obj.search(cr, uid, [
1890 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1891 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1894 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1895 user_id = attendee_data['user_id']
1897 res.update({user_id:status})
1899 #TOCHECK: Delegated Event
1901 if user_id not in res:
1902 res[user_id] = 'free'
1906 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1908 Get User Availability Function
1909 @param self: The object pointer
1910 @param cr: the current row, from the database cursor,
1911 @param uid: the current user’s ID for security checks,
1912 @param ids: List of res user’s IDs.
1913 @param context: A standard dictionary for contextual values
1916 return self._get_user_avail(cr, uid, ids, context=context)
1919 'availability': fields.function(_get_user_avail_fun, type='selection', \
1920 selection=[('free', 'Free'), ('busy', 'Busy')], \
1921 string='Free/Busy', method=True),
1927 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: