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.
110 obj = self.pool.get('res.request.link')
111 ids = obj.search(cr, uid, [])
112 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
113 return [(r['object'], r['name']) for r in res]
115 html_invitation = """
118 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
119 <title>%(name)s</title>
122 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
123 style="font-family: Arial, Sans-serif; font-size: 14">
125 <td width="100%%">Hello,</td>
128 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
131 <td width="100%%">Below are the details of event:</td>
135 <table cellspacing="0" cellpadding="5" border="0" summary=""
136 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
137 <tr valign="center" align="center">
138 <td bgcolor="DFDFDF">
144 <table cellpadding="8" cellspacing="0" border="0"
145 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
149 <div><b>Start Date</b></div>
152 <td>%(start_date)s</td>
154 <div><b>End Date</b></div>
157 <td width="25%%">%(end_date)s</td>
160 <td><b>Description</b></td>
162 <td colspan="3">%(description)s</td>
166 <div><b>Location</b></div>
169 <td colspan="3">%(location)s</td>
173 <div><b>Event Attendees</b></div>
178 <div>%(attendees)s</div>
186 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
187 style="font-family: Arial, Sans-serif; font-size: 14">
189 <td width="100%%">From:</td>
192 <td width="100%%">%(user)s</td>
195 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
198 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
205 class calendar_attendee(osv.osv):
207 Calendar Attendee Information
209 _name = 'calendar.attendee'
210 _description = 'Attendee information'
215 def _get_address(self, name=None, email=None):
217 Gives email information in ical CAL-ADDRESS type format
218 @param name: Name for CAL-ADDRESS value
219 @param email: Email address for CAL-ADDRESS value
223 return (name or '') + (email and ('MAILTO:' + email) or '')
225 def _compute_data(self, cr, uid, ids, name, arg, context):
227 Compute data on function fields for attendee values .
228 @param cr: the current row, from the database cursor,
229 @param uid: the current user’s ID for security checks,
230 @param ids: List of calendar attendee’s IDs.
231 @param name: name of field.
232 @param context: A standard dictionary for contextual values
233 @return: Dictionary of form {id: {'field Name': value'}}.
237 for attdata in self.browse(cr, uid, ids, context=context):
240 if name == 'sent_by':
241 if not attdata.sent_by_uid:
242 result[id][name] = ''
245 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
246 attdata.sent_by_uid.address_id.email)
250 result[id][name] = attdata.user_id.name
251 elif attdata.partner_address_id:
252 result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
254 result[id][name] = attdata.email or ''
256 if name == 'delegated_to':
258 for child in attdata.child_ids:
260 todata.append('MAILTO:' + child.email)
261 result[id][name] = ', '.join(todata)
263 if name == 'delegated_from':
265 for parent in attdata.parent_ids:
267 fromdata.append('MAILTO:' + parent.email)
268 result[id][name] = ', '.join(fromdata)
270 if name == 'event_date':
272 result[id][name] = attdata.ref.date
274 result[id][name] = False
276 if name == 'event_end_date':
278 result[id][name] = attdata.ref.date_deadline
280 result[id][name] = False
282 if name == 'sent_by_uid':
284 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
286 result[id][name] = uid
288 if name == 'language':
289 user_obj = self.pool.get('res.users')
290 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
291 result[id][name] = lang.replace('_', '-')
295 def _links_get(self, cr, uid, context=None):
297 Get request link for ref field in calendar attendee.
298 @param cr: the current row, from the database cursor,
299 @param uid: the current user’s ID for security checks,
300 @param context: A standard dictionary for contextual values
301 @return: list of dictionary which contain object and name and id.
304 obj = self.pool.get('res.request.link')
305 ids = obj.search(cr, uid, [])
306 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
307 return [(r['object'], r['name']) for r in res]
309 def _lang_get(self, cr, uid, context=None):
311 Get language for language selection field.
312 @param cr: the current row, from the database cursor,
313 @param uid: the current user’s ID for security checks,
314 @param context: A standard dictionary for contextual values
315 @return: list of dictionary which contain code and name and id.
317 obj = self.pool.get('res.lang')
318 ids = obj.search(cr, uid, [])
319 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
320 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
324 'cutype': fields.selection([('individual', 'Individual'), \
325 ('group', 'Group'), ('resource', 'Resource'), \
326 ('room', 'Room'), ('unknown', '') ], \
327 'Invite Type', help="Specify the type of Invitation"),
328 'member': fields.char('Member', size=124,
329 help="Indicate the groups that the attendee belongs to"),
330 'role': fields.selection([('req-participant', 'Participation required'), \
331 ('chair', 'Chair Person'), \
332 ('opt-participant', 'Optional Participation'), \
333 ('non-participant', 'For information Purpose')], 'Role', \
334 help='Participation role for the calendar user'),
335 'state': fields.selection([('tentative', 'Tentative'),
336 ('needs-action', 'Needs Action'),
337 ('accepted', 'Accepted'),
338 ('declined', 'Declined'),
339 ('delegated', 'Delegated')], 'State', readonly=True, \
340 help="Status of the attendee's participation"),
341 'rsvp': fields.boolean('Required Reply?',
342 help="Indicats whether the favor of a reply is requested"),
343 'delegated_to': fields.function(_compute_data, method=True, \
344 string='Delegated To', type="char", size=124, store=True, \
345 multi='delegated_to', help="The users that the original \
346 request was delegated to"),
347 'delegated_from': fields.function(_compute_data, method=True, string=\
348 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
349 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
350 'attendee_id', 'parent_id', 'Delegrated From'),
351 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
352 'attendee_id', 'child_id', 'Delegrated To'),
353 'sent_by': fields.function(_compute_data, method=True, string='Sent By', \
354 type="char", multi='sent_by', store=True, size=124, \
355 help="Specify the user that is acting on behalf of the calendar user"),
356 'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', \
357 type="many2one", relation="res.users", multi='sent_by_uid'),
358 'cn': fields.function(_compute_data, method=True, string='Common name', \
359 type="char", size=124, multi='cn', store=True),
360 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
361 that points to the directory information corresponding to the attendee."),
362 'language': fields.function(_compute_data, method=True, string='Language', \
363 type="selection", selection=_lang_get, multi='language', \
364 store=True, help="To specify the language for text values in a\
365 property or property parameter."),
366 'user_id': fields.many2one('res.users', 'User'),
367 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
368 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
369 relation='res.partner', string='Partner', help="Partner related to contact"),
370 'email': fields.char('Email', size=124, help="Email of Invited Person"),
371 'event_date': fields.function(_compute_data, method=True, string='Event Date', \
372 type="datetime", multi='event_date'),
373 'event_end_date': fields.function(_compute_data, method=True, \
374 string='Event End Date', type="datetime", \
375 multi='event_end_date'),
376 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
377 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
380 'state': 'needs-action',
381 'role': 'req-participant',
383 'cutype': 'individual',
386 def copy(self, cr, uid, id, default=None, context=None):
387 raise osv.except_osv(_('Warning!'), _('Can not Duplicate'))
389 def get_ics_file(self, cr, uid, event_obj, context=None):
391 Returns iCalendar file for the event invitation
392 @param self: The object pointer
393 @param cr: the current row, from the database cursor,
394 @param uid: the current user’s ID for security checks,
395 @param event_obj: Event object (browse record)
396 @param context: A standard dictionary for contextual values
397 @return: .ics file content
400 def ics_datetime(idate, short=False):
402 if short or len(idate)<=10:
403 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
405 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
409 # FIXME: why isn't this in CalDAV?
413 cal = vobject.iCalendar()
414 event = cal.add('vevent')
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 '')
467 res = cal.serialize()
470 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
472 Send mail for event invitation to event attendees.
473 @param cr: the current row, from the database cursor,
474 @param uid: the current user’s ID for security checks,
475 @param ids: List of attendee’s IDs.
476 @param email_from: Email address for user sending the mail
477 @param context: A standard dictionary for contextual values
483 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
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)
515 attach=attach and [('invitation.ics', attach)] or None,
521 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
523 Make entry on email and availbility on change of user_id field.
524 @param cr: the current row, from the database cursor,
525 @param uid: the current user’s ID for security checks,
526 @param ids: List of calendar attendee’s IDs.
527 @param user_id: Changed value of User id
528 @return: dictionary of value. which put value in email and availability fields.
532 return {'value': {'email': ''}}
533 usr_obj = self.pool.get('res.users')
534 user = usr_obj.browse(cr, uid, user_id, *args)
535 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
537 def do_tentative(self, cr, uid, ids, context=None, *args):
538 """ Makes event invitation as Tentative
539 @param self: The object pointer
540 @param cr: the current row, from the database cursor,
541 @param uid: the current user’s ID for security checks,
542 @param ids: List of calendar attendee’s IDs
543 @param *args: Get Tupple value
544 @param context: A standard dictionary for contextual values
546 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
548 def do_accept(self, cr, uid, ids, context=None, *args):
550 Update state of invitation as Accepted and
551 if the invited user is other then event user it will make a copy of this event for invited user
552 @param cr: the current row, from the database cursor,
553 @param uid: the current user’s ID for security checks,
554 @param ids: List of calendar attendee’s IDs.
555 @param context: A standard dictionary for contextual values
561 for vals in self.browse(cr, uid, ids, context=context):
562 if vals.ref and vals.ref.user_id:
563 mod_obj = self.pool.get(vals.ref._name)
564 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
565 new_event = mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
566 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
570 def do_decline(self, cr, uid, ids, context=None, *args):
571 """ Marks event invitation as Declined
572 @param self: The object pointer
573 @param cr: the current row, from the database cursor,
574 @param uid: the current user’s ID for security checks,
575 @param ids: List of calendar attendee’s IDs
576 @param *args: Get Tupple value
577 @param context: A standard dictionary for contextual values """
580 return self.write(cr, uid, ids, {'state': 'declined'}, context)
582 def create(self, cr, uid, vals, context=None):
583 """ Overrides orm create method.
584 @param self: The object pointer
585 @param cr: the current row, from the database cursor,
586 @param uid: the current user’s ID for security checks,
587 @param vals: Get Values
588 @param context: A standard dictionary for contextual values """
592 if not vals.get("email") and vals.get("cn"):
593 cnval = vals.get("cn").split(':')
594 email = filter(lambda x:x.__contains__('@'), cnval)
595 vals['email'] = email and email[0] or ''
596 vals['cn'] = vals.get("cn")
597 res = super(calendar_attendee, self).create(cr, uid, vals, context)
601 class res_alarm(osv.osv):
602 """Resource Alarm """
604 _description = 'Basic Alarm Information'
607 'name':fields.char('Name', size=256, required=True),
608 'trigger_occurs': fields.selection([('before', 'Before'), \
609 ('after', 'After')], \
610 'Triggers', required=True),
611 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
612 ('hours', 'Hours'), \
613 ('days', 'Days')], 'Interval', \
615 'trigger_duration': fields.integer('Duration', required=True),
616 'trigger_related': fields.selection([('start', 'The event starts'), \
617 ('end', 'The event ends')], \
618 'Related to', required=True),
619 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
620 are both optional, but if one occurs, so MUST the other"""),
621 'repeat': fields.integer('Repeat'),
622 'active': fields.boolean('Active', help="If the active field is set to \
623 true, it will allow you to hide the event alarm information without removing it.")
626 'trigger_interval': 'minutes',
627 'trigger_duration': 5,
628 'trigger_occurs': 'before',
629 'trigger_related': 'start',
633 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
635 Create Alarm for event.
636 @param cr: the current row, from the database cursor,
637 @param uid: the current user’s ID for security checks,
638 @param ids: List of res alarm’s IDs.
639 @param model: Model name.
640 @param date: Event date
641 @param context: A standard dictionary for contextual values
646 alarm_obj = self.pool.get('calendar.alarm')
647 res_alarm_obj = self.pool.get('res.alarm')
648 ir_obj = self.pool.get('ir.model')
649 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
651 model_obj = self.pool.get(model)
652 for data in model_obj.browse(cr, uid, ids, context):
654 basic_alarm = data.alarm_id
655 cal_alarm = data.base_calendar_alarm_id
656 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
658 # Find for existing res.alarm
659 duration = cal_alarm.trigger_duration
660 interval = cal_alarm.trigger_interval
661 occurs = cal_alarm.trigger_occurs
662 related = cal_alarm.trigger_related
663 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
664 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
667 'trigger_duration': duration,
668 'trigger_interval': interval,
669 'trigger_occurs': occurs,
670 'trigger_related': related,
671 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
673 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
675 new_res_alarm = alarm_ids[0]
676 cr.execute('UPDATE %s ' % model_obj._table + \
677 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
679 (cal_alarm.id, new_res_alarm, data.id))
681 self.do_alarm_unlink(cr, uid, [data.id], model)
685 'description': data.description,
687 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
688 'trigger_related': basic_alarm.trigger_related,
689 'trigger_duration': basic_alarm.trigger_duration,
690 'trigger_occurs': basic_alarm.trigger_occurs,
691 'trigger_interval': basic_alarm.trigger_interval,
692 'duration': basic_alarm.duration,
693 'repeat': basic_alarm.repeat,
695 'event_date': data[date],
697 'model_id': model_id,
700 alarm_id = alarm_obj.create(cr, uid, vals)
701 cr.execute('UPDATE %s ' % model_obj._table + \
702 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
704 ( alarm_id, basic_alarm.id, data.id) )
707 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
709 Delete alarm specified in ids
710 @param cr: the current row, from the database cursor,
711 @param uid: the current user’s ID for security checks,
712 @param ids: List of res alarm’s IDs.
713 @param model: Model name for which alarm is to be cleared.
718 alarm_obj = self.pool.get('calendar.alarm')
719 ir_obj = self.pool.get('ir.model')
720 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
721 model_obj = self.pool.get(model)
722 for datas in model_obj.browse(cr, uid, ids, context):
723 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
725 alarm_obj.unlink(cr, uid, alarm_ids)
726 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
727 where id=%%s' % model_obj._table,(datas.id,))
732 class calendar_alarm(osv.osv):
733 _name = 'calendar.alarm'
734 _description = 'Event alarm information'
735 _inherit = 'res.alarm'
739 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
740 'name': fields.char('Summary', size=124, help="""Contains the text to be \
741 used as the message subject for email \
742 or contains the text to be used for display"""),
743 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
744 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
745 required=True, help="Defines the action to be invoked when an alarm is triggered"),
746 'description': fields.text('Description', help='Provides a more complete \
747 description of the calendar component, than that \
748 provided by the "SUMMARY" property'),
749 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
750 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
751 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
752 which is rendered when the alarm is triggered for audio,
753 * File which is intended to be sent as message attachments for email,
754 * Points to a procedure resource, which is invoked when\
755 the alarm is triggered for procedure."""),
756 'res_id': fields.integer('Resource ID'),
757 'model_id': fields.many2one('ir.model', 'Model'),
758 'user_id': fields.many2one('res.users', 'Owner'),
759 'event_date': fields.datetime('Event Date'),
760 'event_end_date': fields.datetime('Event End Date'),
761 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
762 'state':fields.selection([
767 ], 'State', select=True, readonly=True),
775 def create(self, cr, uid, vals, context=None):
777 Overrides orm create method.
778 @param self: The object pointer
779 @param cr: the current row, from the database cursor,
780 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
781 @param context: A standard dictionary for contextual values
782 @return: new record id for calendar_alarm.
786 event_date = vals.get('event_date', False)
788 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
789 if vals['trigger_interval'] == 'days':
790 delta = timedelta(days=vals['trigger_duration'])
791 if vals['trigger_interval'] == 'hours':
792 delta = timedelta(hours=vals['trigger_duration'])
793 if vals['trigger_interval'] == 'minutes':
794 delta = timedelta(minutes=vals['trigger_duration'])
795 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
796 vals['trigger_date'] = trigger_date
797 res = super(calendar_alarm, self).create(cr, uid, vals, context)
800 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
802 """Scheduler for event reminder
803 @param self: The object pointer
804 @param cr: the current row, from the database cursor,
805 @param uid: the current user’s ID for security checks,
806 @param ids: List of calendar alarm’s IDs.
807 @param use_new_cursor: False or the dbname
808 @param context: A standard dictionary for contextual values
810 return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
813 current_datetime = datetime.now()
814 request_obj = self.pool.get('res.request')
815 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
819 for alarm in self.browse(cr, uid, alarm_ids, context=context):
820 next_trigger_date = None
822 model_obj = self.pool.get(alarm.model_id.model)
823 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
827 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
828 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
830 trigger_interval = alarm.trigger_interval
831 if trigger_interval == 'days':
832 delta = timedelta(days=alarm.trigger_duration)
833 if trigger_interval == 'hours':
834 delta = timedelta(hours=alarm.trigger_duration)
835 if trigger_interval == 'minutes':
836 delta = timedelta(minutes=alarm.trigger_duration)
837 delta = alarm.trigger_occurs == 'after' and delta or -delta
839 for rdate in recurrent_dates:
840 if rdate + delta > current_datetime:
842 if rdate + delta <= current_datetime:
843 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
844 rest_dates = recurrent_dates[len(re_dates):]
845 next_trigger_date = rest_dates and rest_dates[0] or None
848 re_dates = [alarm.trigger_date]
850 for r_date in re_dates:
851 ref = alarm.model_id.model + ',' + str(alarm.res_id)
853 # search for alreay sent requests
854 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
857 if alarm.action == 'display':
860 'act_from': alarm.user_id.id,
861 'act_to': alarm.user_id.id,
862 'body': alarm.description,
863 'trigger_date': r_date,
866 request_id = request_obj.create(cr, uid, value)
867 request_ids = [request_id]
868 for attendee in res_obj.attendee_ids:
870 value['act_to'] = attendee.user_id.id
871 request_id = request_obj.create(cr, uid, value)
872 request_ids.append(request_id)
873 request_obj.request_send(cr, uid, request_ids)
875 if alarm.action == 'email':
876 sub = '[Openobject Reminder] %s' % (alarm.name)
888 """ % (alarm.name, alarm.trigger_date, alarm.description, \
889 alarm.user_id.name, alarm.user_id.signature)
890 mail_to = [alarm.user_id.address_id.email]
891 for att in alarm.attendee_ids:
892 mail_to.append(att.user_id.address_id.email)
895 tools.config.get('email_from', False),
900 if next_trigger_date:
901 update_vals.update({'trigger_date': next_trigger_date})
903 update_vals.update({'state': 'done'})
904 self.write(cr, uid, [alarm.id], update_vals)
910 class calendar_event(osv.osv):
911 _name = "calendar.event"
912 _description = "Calendar Event"
915 def _tz_get(self, cr, uid, context=None):
916 return [(x.lower(), x) for x in pytz.all_timezones]
918 def onchange_allday(self, cr, uid, ids, allday, context=None):
919 """Sets duration as 24 Hours if event is selected for all day
920 @param self: The object pointer
921 @param cr: the current row, from the database cursor,
922 @param uid: the current user’s ID for security checks,
923 @param ids: List of calendar event’s IDs.
924 @param allday: Value of allday boolean
925 @param context: A standard dictionary for contextual values
927 if not allday or not ids:
929 event = self.browse(cr, uid, ids, context=context)[0]
933 return {'value': value}
935 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
936 """Returns duration and/or end date based on values passed
937 @param self: The object pointer
938 @param cr: the current row, from the database cursor,
939 @param uid: the current user’s ID for security checks,
940 @param ids: List of calendar event’s IDs.
941 @param start_date: Starting date
942 @param duration: Duration between start date and end date
943 @param end_date: Ending Datee
944 @param context: A standard dictionary for contextual values
952 if not end_date and not duration:
954 value['duration'] = duration
956 if allday: # For all day event
957 value = {'duration': 24}
960 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
961 if end_date and not duration:
962 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
964 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
965 value['duration'] = round(duration, 2)
967 end = start + timedelta(hours=duration)
968 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
969 elif end_date and duration and not allday:
970 # we have both, keep them synchronized:
971 # set duration based on end_date (arbitrary decision: this avoid
972 # getting dates like 06:31:48 instead of 06:32:00)
973 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
975 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
976 value['duration'] = round(duration, 2)
978 return {'value': value}
980 def unlink_events(self, cr, uid, ids, context=None):
982 This function deletes event which are linked with the event with recurrent_uid
983 (Removes the events which refers to the same UID value)
988 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
989 r_ids = map(lambda x: x[0], cr.fetchall())
990 self.unlink(cr, uid, r_ids, context=context)
993 def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
995 Sets values of fields that defines event recurrence from the value of rrule string
996 @param self: The object pointer
997 @param cr: the current row, from the database cursor,
998 @param id: List of calendar event's ids.
999 @param context: A standard dictionary for contextual values
1000 @return: dictionary of rrule value.
1004 cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
1005 mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
1006 day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
1009 cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1012 for part in value.split(';'):
1013 if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1014 rrule_type = part.lower()[5:]
1017 rrule_type = 'custom'
1019 ans = value.split(';')
1021 val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1022 if not val.get('interval'):
1023 rrule_type = 'custom'
1024 elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1025 rrule_type = 'custom'
1027 qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1028 qry_args = [ rrule_type, ]
1030 if rrule_type == 'custom':
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 elif datas.get('rrule_type') == 'custom':
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'), ('custom', 'Custom')],
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'), \
1151 ('daily', 'Days'), \
1152 ('weekly', 'Weeks'), \
1153 ('monthly', 'Months'), \
1154 ('yearly', 'Years'), \
1155 ('secondly', 'Seconds'), \
1156 ('minutely', 'Minutes') ], 'Frequency'),
1157 'interval': fields.integer('Interval', help="Repeat every x"),
1158 'count': fields.integer('Count', help="Repeat max that 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.")
1184 def default_organizer(self, cr, uid, context=None):
1185 user_pool = self.pool.get('res.users')
1186 user = user_pool.browse(cr, uid, uid, context=context)
1189 res += " <%s>" %(user.user_email)
1193 'state': 'tentative',
1200 'user_id': lambda self, cr, uid, ctx: uid,
1201 'organizer': default_organizer,
1204 def open_event(self, cr, uid, ids, context=None):
1206 Open Event From for Editing
1207 @param cr: the current row, from the database cursor,
1208 @param uid: the current user’s ID for security checks,
1209 @param ids: List of event’s IDs
1210 @param context: A standard dictionary for contextual values
1211 @return: Dictionary value which open Crm Meeting form.
1216 data_obj = self.pool.get('ir.model.data')
1220 id2 = data_obj._get_id(cr, uid, 'base_calendar', 'event_form_view')
1221 id3 = data_obj._get_id(cr, uid, 'base_calendar', 'event_tree_view')
1222 id4 = data_obj._get_id(cr, uid, 'base_calendar', 'event_calendar_view')
1224 id2 = data_obj.browse(cr, uid, id2, context=context).res_id
1226 id3 = data_obj.browse(cr, uid, id3, context=context).res_id
1228 id4 = data_obj.browse(cr, uid, id4, context=context).res_id
1232 'view_type': 'form',
1233 'view_mode': 'form,tree',
1234 'res_model': 'calendar.event',
1236 'views': [(id2, 'form'), (id3, 'tree'), (id4, 'calendar')],
1237 'type': 'ir.actions.act_window',
1238 'res_id': base_calendar_id2real_id(id),
1244 def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1246 Modifies the recurring event
1247 @param cr: the current row, from the database cursor,
1248 @param uid: the current user’s ID for security checks,
1249 @param event_ids: List of crm meeting’s IDs.
1250 @param context: A standard dictionary for contextual values
1253 for event_id in event_ids:
1254 event_id = base_calendar_id2real_id(event_id)
1257 defaults.update({'table': self._table})
1259 qry = "UPDATE %(table)s set name = '%(name)s', \
1260 date = '%(date)s', date_deadline = '%(date_deadline)s'"
1261 if defaults.get('alarm_id'):
1262 qry += ", alarm_id = %(alarm_id)s"
1263 if defaults.get('location'):
1264 qry += ", location = '%(location)s'"
1265 qry += "WHERE id = %s" % (event_id)
1266 cr.execute(qry, defaults)
1270 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
1271 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1272 This method gives ids of dates that comes between start date and end date of calendar views
1273 @param self: The object pointer
1274 @param cr: the current row, from the database cursor,
1275 @param uid: the current user’s ID for security checks,
1276 @param base_start_date: Get Start Date
1277 @param base_until_date: Get End Date
1278 @param limit: The Number of Results to Return """
1282 if isinstance(select, (str, int, long)):
1288 if ids and (base_start_date or base_until_date):
1289 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1290 m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1291 " m where m.id = ANY(%s)", (ids,) )
1294 for data in cr.dictfetchall():
1295 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1296 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1299 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1300 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1301 start_date = event_date
1302 if not data['rrule']:
1303 if start_date and (event_date < start_date):
1305 if until_date and (event_date > until_date):
1307 idval = real_id2base_calendar_id(data['id'], data['date'])
1308 if not data['recurrent_id']:
1309 result.append(idval)
1312 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1313 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1314 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1315 if ls[1] == data['recurrent_id']:
1316 result.append(idval)
1317 recur_dict.append(ex_id)
1319 exdate = data['exdate'] and data['exdate'].split(',') or []
1320 rrule_str = data['rrule']
1322 rrule_until_date = False
1324 for rule in rrule_str.split(';'):
1325 name, value = rule.split('=')
1328 value = parser.parse(value)
1329 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1330 if until_date and until_date >= rrule_until_date:
1331 until_date = rrule_until_date
1333 value = until_date.strftime("%Y%m%d%H%M%S")
1334 new_rule = '%s=%s' % (name, value)
1335 new_rrule_str.append(new_rule)
1336 if not is_until and until_date:
1337 value = until_date.strftime("%Y%m%d%H%M%S")
1339 new_rule = '%s=%s' % (name, value)
1340 new_rrule_str.append(new_rule)
1341 new_rrule_str = ';'.join(new_rrule_str)
1342 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1343 for r_date in rdates:
1344 if start_date and r_date < start_date:
1346 if until_date and r_date > until_date:
1348 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1349 result.append(idval)
1352 ids = list(set(result)-set(recur_dict))
1353 if isinstance(select, (str, int, long)):
1354 return ids and ids[0] or False
1357 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1359 Compute rule string according to value type RECUR of iCalendar from the values given.
1360 @param self: the object pointer
1361 @param cr: the current row, from the database cursor,
1362 @param uid: the current user’s ID for security checks,
1363 @param datas: dictionary of freq and interval value.
1364 @param context: A standard dictionary for contextual values
1365 @return: String value of the format RECUR of iCalendar
1368 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1373 freq = datas.get('freq')
1377 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1379 if freq == 'weekly':
1381 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1383 weekstring = ';BYDAY=' + ','.join(byday)
1385 elif freq == 'monthly':
1386 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1387 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1388 if datas.get('select1')=='day':
1389 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1390 elif datas.get('select1')=='date':
1391 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1393 elif freq == 'yearly':
1394 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1395 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1396 bymonth = ';BYMONTH=' + str(datas.get('month_list'))
1397 if datas.get('select1')=='day':
1398 bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1399 elif datas.get('select1')=='date':
1400 bystring = ';BYMONTHDAY=' + str(datas.get('day'))
1401 yearstring = bymonth + bystring
1403 if datas.get('end_date'):
1404 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1405 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1406 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1408 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1409 + enddate + monthstring + yearstring
1413 def search(self, cr, uid, args, offset=0, limit=100, order=None,
1414 context=None, count=False):
1416 Overrides orm search method.
1417 @param cr: the current row, from the database cursor,
1418 @param user: the current user’s ID for security checks,
1419 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1420 @param offset: The Number of Results to Pass
1421 @param limit: The Number of Results to Return
1422 @param context: A standard dictionary for contextual values
1423 @param count: If its True the method returns number of records instead of ids
1426 args_without_date = []
1431 if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1432 args_without_date.append(arg)
1434 if arg[1] in ('>', '>='):
1438 elif arg[1] in ('<', '<='):
1442 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1443 offset, limit, order, context, count)
1445 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
1448 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1450 Overrides orm write method.
1451 @param self: the object pointer
1452 @param cr: the current row, from the database cursor,
1453 @param uid: the current user’s ID for security checks,
1454 @param ids: List of crm meeting's ids
1455 @param vals: Dictionary of field value.
1456 @param context: A standard dictionary for contextual values
1461 if isinstance(ids, (str, int, long)):
1467 for event_id in select:
1468 real_event_id = base_calendar_id2real_id(event_id)
1469 if len(str(event_id).split('-')) > 1:
1470 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1471 'rrule', 'duration'])
1472 if data.get('rrule'):
1474 'recurrent_uid': real_event_id,
1475 'recurrent_id': data.get('date'),
1476 'rrule_type': 'none',
1480 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1481 context.update({'active_id': new_id, 'active_ids': [new_id]})
1483 if not real_event_id in new_ids:
1484 new_ids.append(real_event_id)
1486 if vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1487 vals['vtimezone'] = vals['vtimezone'][40:]
1489 updated_vals = self.onchange_dates(cr, uid, new_ids,
1490 vals.get('date', False),
1491 vals.get('duration', False),
1492 vals.get('date_deadline', False),
1493 vals.get('allday', False),
1495 vals.update(updated_vals.get('value', {}))
1498 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1500 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1501 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1502 # change alarm details
1503 alarm_obj = self.pool.get('res.alarm')
1504 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1507 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1509 Overrides orm browse method.
1510 @param self: the object pointer
1511 @param cr: the current row, from the database cursor,
1512 @param uid: the current user’s ID for security checks,
1513 @param ids: List of crm meeting's ids
1514 @param context: A standard dictionary for contextual values
1515 @return: the object list.
1517 if isinstance(ids, (str, int, long)):
1521 select = map(lambda x: base_calendar_id2real_id(x), select)
1522 res = super(calendar_event, self).browse(cr, uid, select, context, \
1523 list_class, fields_process)
1524 if isinstance(ids, (str, int, long)):
1525 return res and res[0] or False
1529 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1531 Overrides orm Read method.Read List of fields for calendar event.
1532 @param cr: the current row, from the database cursor,
1533 @param user: the current user’s ID for security checks,
1534 @param ids: List of calendar event's id.
1535 @param fields: List of fields.
1536 @param context: A standard dictionary for contextual values
1537 @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1539 # FIXME This whole id mangling has to go!
1543 if isinstance(ids, (str, int, long)):
1547 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1549 if fields and 'date' not in fields:
1550 fields.append('date')
1551 if fields and 'duration' not in fields:
1552 fields.append('duration')
1555 for base_calendar_id, real_id in select:
1556 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1557 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1558 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1559 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1561 res['date_deadline'] = ls[2]
1562 res['id'] = base_calendar_id
1565 if isinstance(ids, (str, int, long)):
1566 return result and result[0] or False
1569 def copy(self, cr, uid, id, default=None, context=None):
1571 Duplicate record on specified id.
1572 @param self: the object pointer.
1573 @param cr: the current row, from the database cursor,
1574 @param id: id of record from which we duplicated.
1575 @param context: A standard dictionary for contextual values
1576 @return: Duplicate record id.
1580 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1581 alarm_obj = self.pool.get('res.alarm')
1582 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1586 def unlink(self, cr, uid, ids, context=None):
1588 Deletes records specified in ids.
1589 @param self: the object pointer.
1590 @param cr: the current row, from the database cursor,
1591 @param id: List of calendar event's id.
1592 @param context: A standard dictionary for contextual values
1596 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1597 event_id = event_datas['id']
1598 if isinstance(event_id, (int, long)):
1599 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1600 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1601 self.unlink_events(cr, uid, [event_id], context=context)
1603 str_event, date_new = event_id.split('-')
1604 event_id = int(str_event)
1605 if event_datas['rrule']:
1606 # Remove one of the recurrent event
1607 date_new = time.strftime("%Y%m%dT%H%M%S", \
1608 time.strptime(date_new, "%Y%m%d%H%M%S"))
1609 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1610 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1612 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1613 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1614 self.unlink_events(cr, uid, [event_id], context=context)
1617 def create(self, cr, uid, vals, context=None):
1620 @param self: the object pointer
1621 @param cr: the current row, from the database cursor,
1622 @param uid: the current user’s ID for security checks,
1623 @param vals: dictionary of every field value.
1624 @param context: A standard dictionary for contextual values
1625 @return: new created record id.
1630 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1631 vals['vtimezone'] = vals['vtimezone'][40:]
1633 updated_vals = self.onchange_dates(cr, uid, [],
1634 vals.get('date', False),
1635 vals.get('duration', False),
1636 vals.get('date_deadline', False),
1637 vals.get('allday', False),
1639 vals.update(updated_vals.get('value', {}))
1641 res = super(calendar_event, self).create(cr, uid, vals, context)
1642 alarm_obj = self.pool.get('res.alarm')
1643 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1646 def do_tentative(self, cr, uid, ids, context=None, *args):
1647 """ Makes event invitation as Tentative
1648 @param self: The object pointer
1649 @param cr: the current row, from the database cursor,
1650 @param uid: the current user’s ID for security checks,
1651 @param ids: List of Event IDs
1652 @param *args: Get Tupple value
1653 @param context: A standard dictionary for contextual values
1655 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1657 def do_cancel(self, cr, uid, ids, context=None, *args):
1658 """ Makes event invitation as Tentative
1659 @param self: The object pointer
1660 @param cr: the current row, from the database cursor,
1661 @param uid: the current user’s ID for security checks,
1662 @param ids: List of Event IDs
1663 @param *args: Get Tupple value
1664 @param context: A standard dictionary for contextual values
1666 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1668 def do_confirm(self, cr, uid, ids, context=None, *args):
1669 """ Makes event invitation as Tentative
1670 @param self: The object pointer
1671 @param cr: the current row, from the database cursor,
1672 @param uid: the current user’s ID for security checks,
1673 @param ids: List of Event IDs
1674 @param *args: Get Tupple value
1675 @param context: A standard dictionary for contextual values
1677 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1681 class calendar_todo(osv.osv):
1682 """ Calendar Task """
1684 _name = "calendar.todo"
1685 _inherit = "calendar.event"
1686 _description = "Calendar Task"
1688 def _get_date(self, cr, uid, ids, name, arg, context):
1691 @param self: The object pointer
1692 @param cr: the current row, from the database cursor,
1693 @param uid: the current user’s ID for security checks,
1694 @param ids: List of calendar todo's IDs.
1695 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1696 @param context: A standard dictionary for contextual values
1700 for event in self.browse(cr, uid, ids, context=context):
1701 res[event.id] = event.date_start
1704 def _set_date(self, cr, uid, id, name, value, arg, context):
1707 @param self: The object pointer
1708 @param cr: the current row, from the database cursor,
1709 @param uid: the current user’s ID for security checks,
1710 @param id: calendar's ID.
1711 @param value: Get Value
1712 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1713 @param context: A standard dictionary for contextual values
1716 assert name == 'date'
1717 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1720 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1721 string='Duration', store=True, type='datetime'),
1722 'duration': fields.integer('Duration'),
1730 class ir_attachment(osv.osv):
1731 _name = 'ir.attachment'
1732 _inherit = 'ir.attachment'
1734 def search_count(self, cr, user, args, context=None):
1736 @param self: The object pointer
1737 @param cr: the current row, from the database cursor,
1738 @param user: the current user’s ID for security checks,
1739 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1740 @param context: A standard dictionary for contextual values
1745 args1.append(map(lambda x:str(x).split('-')[0], arg))
1746 return super(ir_attachment, self).search_count(cr, user, args1, context)
1748 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1749 context=None, count=False):
1751 @param self: The object pointer
1752 @param cr: the current row, from the database cursor,
1753 @param uid: the current user’s ID for security checks,
1754 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1755 @param offset: The Number of Results to pass,
1756 @param limit: The Number of Results to Return,
1757 @param context: A standard dictionary for contextual values
1761 for i, arg in enumerate(new_args):
1762 if arg[0] == 'res_id':
1763 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1764 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1765 limit=limit, order=order,
1766 context=context, count=False)
1769 class ir_values(osv.osv):
1770 _inherit = 'ir.values'
1772 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1773 isobject=False, meta=False, preserve_user=False, company=False):
1776 @param self: The object pointer
1777 @param cr: the current row, from the database cursor,
1778 @param uid: the current user’s ID for security checks,
1779 @param model: Get The Model
1784 if type(data) in (list, tuple):
1785 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1787 new_model.append(data)
1788 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1789 value, replace, isobject, meta, preserve_user, company)
1791 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1792 res_id_req=False, without_user=True, key2_req=True):
1795 @param self: The object pointer
1796 @param cr: the current row, from the database cursor,
1797 @param uid: the current user’s ID for security checks,
1798 @param model: Get The Model
1804 if type(data) in (list, tuple):
1805 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1807 new_model.append(data)
1808 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1809 meta, context, res_id_req, without_user, key2_req)
1813 class ir_model(osv.osv):
1815 _inherit = 'ir.model'
1817 def read(self, cr, uid, ids, fields=None, context=None,
1818 load='_classic_read'):
1820 Overrides orm read method.
1821 @param self: The object pointer
1822 @param cr: the current row, from the database cursor,
1823 @param uid: the current user’s ID for security checks,
1824 @param ids: List of IR Model’s IDs.
1825 @param context: A standard dictionary for contextual values
1827 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1830 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1831 context=context, load=load)
1834 val['id'] = base_calendar_id2real_id(val['id'])
1835 return isinstance(ids, (str, int, long)) and data[0] or data
1839 class virtual_report_spool(web_services.report_spool):
1841 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1844 @param self: The object pointer
1845 @param db: get the current database,
1846 @param uid: the current user’s ID for security checks,
1847 @param context: A standard dictionary for contextual values
1850 if object == 'printscreen.list':
1851 return super(virtual_report_spool, self).exp_report(db, uid, \
1852 object, ids, datas, context)
1855 new_ids.append(base_calendar_id2real_id(id))
1856 if datas.get('id', False):
1857 datas['id'] = base_calendar_id2real_id(datas['id'])
1858 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1860 virtual_report_spool()
1862 class res_users(osv.osv):
1863 _inherit = 'res.users'
1865 def _get_user_avail(self, cr, uid, ids, context=None):
1867 Get User Availability
1868 @param self: The object pointer
1869 @param cr: the current row, from the database cursor,
1870 @param uid: the current user’s ID for security checks,
1871 @param ids: List of res user’s IDs.
1872 @param context: A standard dictionary for contextual values
1875 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1877 attendee_obj = self.pool.get('calendar.attendee')
1878 attendee_ids = attendee_obj.search(cr, uid, [
1879 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1880 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1883 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1884 user_id = attendee_data['user_id']
1886 res.update({user_id:status})
1888 #TOCHECK: Delegated Event
1890 if user_id not in res:
1891 res[user_id] = 'free'
1895 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1897 Get User Availability Function
1898 @param self: The object pointer
1899 @param cr: the current row, from the database cursor,
1900 @param uid: the current user’s ID for security checks,
1901 @param ids: List of res user’s IDs.
1902 @param context: A standard dictionary for contextual values
1905 return self._get_user_avail(cr, uid, ids, context=context)
1908 'availability': fields.function(_get_user_avail_fun, type='selection', \
1909 selection=[('free', 'Free'), ('busy', 'Busy')], \
1910 string='Free/Busy', method=True),
1916 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: