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))
64 return list(rset1._iter())
66 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
68 This function converts virtual event id into real id of actual event
69 @param base_calendar_id: Id of calendar
70 @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
73 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
74 res = base_calendar_id.split('-')
79 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
80 time.strptime(res[1], "%Y%m%d%H%M%S"))
81 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
82 end = start + timedelta(hours=with_date)
83 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
86 return base_calendar_id and int(base_calendar_id) or base_calendar_id
88 def real_id2base_calendar_id(real_id, recurrent_date):
90 Convert real id of record into virtual id using recurrent_date
91 e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
93 @return: real id with recurrent date.
96 if real_id and recurrent_date:
97 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
98 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
99 return '%d-%s' % (real_id, recurrent_date)
102 def _links_get(self, cr, uid, context=None):
105 @param cr: the current row, from the database cursor,
106 @param uid: the current user’s ID for security checks,
107 @param context: A standard dictionary for contextual values
108 @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=None):
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.
303 obj = self.pool.get('res.request.link')
304 ids = obj.search(cr, uid, [])
305 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
306 return [(r['object'], r['name']) for r in res]
308 def _lang_get(self, cr, uid, context=None):
310 Get language for language selection field.
311 @param cr: the current row, from the database cursor,
312 @param uid: the current user’s ID for security checks,
313 @param context: A standard dictionary for contextual values
314 @return: list of dictionary which contain code and name and id.
316 obj = self.pool.get('res.lang')
317 ids = obj.search(cr, uid, [])
318 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
319 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
323 'cutype': fields.selection([('individual', 'Individual'), \
324 ('group', 'Group'), ('resource', 'Resource'), \
325 ('room', 'Room'), ('unknown', '') ], \
326 'Invite Type', help="Specify the type of Invitation"),
327 'member': fields.char('Member', size=124,
328 help="Indicate the groups that the attendee belongs to"),
329 'role': fields.selection([('req-participant', 'Participation required'), \
330 ('chair', 'Chair Person'), \
331 ('opt-participant', 'Optional Participation'), \
332 ('non-participant', 'For information Purpose')], 'Role', \
333 help='Participation role for the calendar user'),
334 'state': fields.selection([('tentative', 'Tentative'),
335 ('needs-action', 'Needs Action'),
336 ('accepted', 'Accepted'),
337 ('declined', 'Declined'),
338 ('delegated', 'Delegated')], 'State', readonly=True, \
339 help="Status of the attendee's participation"),
340 'rsvp': fields.boolean('Required Reply?',
341 help="Indicats whether the favor of a reply is requested"),
342 'delegated_to': fields.function(_compute_data, method=True, \
343 string='Delegated To', type="char", size=124, store=True, \
344 multi='delegated_to', help="The users that the original \
345 request was delegated to"),
346 'delegated_from': fields.function(_compute_data, method=True, string=\
347 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
348 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
349 'attendee_id', 'parent_id', 'Delegrated From'),
350 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
351 'attendee_id', 'child_id', 'Delegrated To'),
352 'sent_by': fields.function(_compute_data, method=True, string='Sent By', \
353 type="char", multi='sent_by', store=True, size=124, \
354 help="Specify the user that is acting on behalf of the calendar user"),
355 'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', \
356 type="many2one", relation="res.users", multi='sent_by_uid'),
357 'cn': fields.function(_compute_data, method=True, string='Common name', \
358 type="char", size=124, multi='cn', store=True),
359 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
360 that points to the directory information corresponding to the attendee."),
361 'language': fields.function(_compute_data, method=True, string='Language', \
362 type="selection", selection=_lang_get, multi='language', \
363 store=True, help="To specify the language for text values in a\
364 property or property parameter."),
365 'user_id': fields.many2one('res.users', 'User'),
366 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
367 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
368 relation='res.partner', string='Partner', help="Partner related to contact"),
369 'email': fields.char('Email', size=124, help="Email of Invited Person"),
370 'event_date': fields.function(_compute_data, method=True, string='Event Date', \
371 type="datetime", multi='event_date'),
372 'event_end_date': fields.function(_compute_data, method=True, \
373 string='Event End Date', type="datetime", \
374 multi='event_end_date'),
375 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
376 '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 if not event_obj.date_deadline or not event_obj.date:
416 raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
417 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
418 event.add('dtstart').value = ics_datetime(event_obj.date)
419 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
420 event.add('summary').value = event_obj.name
421 if event_obj.description:
422 event.add('description').value = event_obj.description
423 if event_obj.location:
424 event.add('location').value = event_obj.location
426 event.add('rrule').value = event_obj.rrule
427 if event_obj.organizer:
428 event_org = event.add('organizer')
429 event_org.params['CN'] = [event_obj.organizer]
430 event_org.value = 'MAILTO:' + (event_obj.organizer)
431 elif event_obj.user_id or event_obj.organizer_id:
432 event_org = event.add('organizer')
433 organizer = event_obj.organizer_id
435 organizer = event_obj.user_id
436 event_org.params['CN'] = [organizer.name]
437 event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
439 if event_obj.alarm_id:
440 # computes alarm data
441 valarm = event.add('valarm')
442 alarm_object = self.pool.get('res.alarm')
443 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
444 # Compute trigger data
445 interval = alarm_data['trigger_interval']
446 occurs = alarm_data['trigger_occurs']
447 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
448 or -(alarm_data['trigger_duration'])
449 related = alarm_data['trigger_related']
450 trigger = valarm.add('TRIGGER')
451 trigger.params['related'] = [related.upper()]
452 if interval == 'days':
453 delta = timedelta(days=duration)
454 if interval == 'hours':
455 delta = timedelta(hours=duration)
456 if interval == 'minutes':
457 delta = timedelta(minutes=duration)
458 trigger.value = delta
459 # Compute other details
460 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
462 for attendee in event_obj.attendee_ids:
463 attendee_add = event.add('attendee')
464 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
465 attendee_add.params['ROLE'] = [str(attendee.role)]
466 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
467 attendee_add.value = 'MAILTO:' + (attendee.email or '')
468 res = cal.serialize()
471 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
473 Send mail for event invitation to event attendees.
474 @param cr: the current row, from the database cursor,
475 @param uid: the current user’s ID for security checks,
476 @param ids: List of attendee’s IDs.
477 @param email_from: Email address for user sending the mail
478 @param context: A standard dictionary for contextual values
484 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
485 for att in self.browse(cr, uid, ids, context=context):
486 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
487 sign = '<br>'.join(sign and sign.split('\n') or [])
492 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
494 for att2 in self.browse(cr, uid, other_invitation_ids):
495 att_infos.append(((att2.user_id and att2.user_id.name) or \
496 (att2.partner_id and att2.partner_id.name) or \
497 att2.email) + ' - Status: ' + att2.state.title())
498 body_vals = {'name': res_obj.name,
499 'start_date': res_obj.date,
500 'end_date': res_obj.date_deadline or False,
501 'description': res_obj.description or '-',
502 'location': res_obj.location or '-',
503 'attendees': '<br>'.join(att_infos),
504 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
508 body = html_invitation % body_vals
509 if mail_to and email_from:
510 attach = self.get_ics_file(cr, uid, res_obj, context=context)
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 current_datetime = datetime.now()
815 request_obj = self.pool.get('res.request')
816 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
820 for alarm in self.browse(cr, uid, alarm_ids, context=context):
821 next_trigger_date = None
823 model_obj = self.pool.get(alarm.model_id.model)
824 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
828 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
829 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
831 trigger_interval = alarm.trigger_interval
832 if trigger_interval == 'days':
833 delta = timedelta(days=alarm.trigger_duration)
834 if trigger_interval == 'hours':
835 delta = timedelta(hours=alarm.trigger_duration)
836 if trigger_interval == 'minutes':
837 delta = timedelta(minutes=alarm.trigger_duration)
838 delta = alarm.trigger_occurs == 'after' and delta or -delta
840 for rdate in recurrent_dates:
841 if rdate + delta > current_datetime:
843 if rdate + delta <= current_datetime:
844 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
845 rest_dates = recurrent_dates[len(re_dates):]
846 next_trigger_date = rest_dates and rest_dates[0] or None
849 re_dates = [alarm.trigger_date]
851 for r_date in re_dates:
852 ref = alarm.model_id.model + ',' + str(alarm.res_id)
854 # search for alreay sent requests
855 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
858 if alarm.action == 'display':
861 'act_from': alarm.user_id.id,
862 'act_to': alarm.user_id.id,
863 'body': alarm.description,
864 'trigger_date': r_date,
867 request_id = request_obj.create(cr, uid, value)
868 request_ids = [request_id]
869 for attendee in res_obj.attendee_ids:
871 value['act_to'] = attendee.user_id.id
872 request_id = request_obj.create(cr, uid, value)
873 request_ids.append(request_id)
874 request_obj.request_send(cr, uid, request_ids)
876 if alarm.action == 'email':
877 sub = '[Openobject Reminder] %s' % (alarm.name)
889 """ % (alarm.name, alarm.trigger_date, alarm.description, \
890 alarm.user_id.name, alarm.user_id.signature)
891 mail_to = [alarm.user_id.address_id.email]
892 for att in alarm.attendee_ids:
893 mail_to.append(att.user_id.address_id.email)
896 tools.config.get('email_from', False),
901 if next_trigger_date:
902 update_vals.update({'trigger_date': next_trigger_date})
904 update_vals.update({'state': 'done'})
905 self.write(cr, uid, [alarm.id], update_vals)
911 class calendar_event(osv.osv):
912 _name = "calendar.event"
913 _description = "Calendar Event"
916 def _tz_get(self, cr, uid, context=None):
917 return [(x.lower(), x) for x in pytz.all_timezones]
919 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
920 """Returns duration and/or end date based on values passed
921 @param self: The object pointer
922 @param cr: the current row, from the database cursor,
923 @param uid: the current user’s ID for security checks,
924 @param ids: List of calendar event’s IDs.
925 @param start_date: Starting date
926 @param duration: Duration between start date and end date
927 @param end_date: Ending Datee
928 @param context: A standard dictionary for contextual values
936 if not end_date and not duration:
938 value['duration'] = duration
940 if allday: # For all day event
941 value = {'duration': 24}
944 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
945 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
946 value['date'] = start_date
950 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
951 if end_date and not duration:
952 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
954 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
955 value['duration'] = round(duration, 2)
957 end = start + timedelta(hours=duration)
958 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
959 elif end_date and duration and not allday:
960 # we have both, keep them synchronized:
961 # set duration based on end_date (arbitrary decision: this avoid
962 # getting dates like 06:31:48 instead of 06:32:00)
963 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
965 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
966 value['duration'] = round(duration, 2)
968 return {'value': value}
970 def unlink_events(self, cr, uid, ids, context=None):
972 This function deletes event which are linked with the event with recurrent_uid
973 (Removes the events which refers to the same UID value)
978 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
979 r_ids = map(lambda x: x[0], cr.fetchall())
980 self.unlink(cr, uid, r_ids, context=context)
983 def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
985 Sets values of fields that defines event recurrence from the value of rrule string
986 @param self: The object pointer
987 @param cr: the current row, from the database cursor,
988 @param id: List of calendar event's ids.
989 @param context: A standard dictionary for contextual values
990 @return: dictionary of rrule value.
994 cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
995 mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
996 day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
999 cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1002 for part in value.split(';'):
1003 if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1004 rrule_type = part.lower()[5:]
1007 rrule_type = 'custom'
1009 ans = value.split(';')
1011 val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1012 if not val.get('interval'):
1013 rrule_type = 'custom'
1014 elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1015 rrule_type = 'custom'
1017 qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1018 qry_args = [ rrule_type, ]
1019 new_val = val.copy()
1020 for k, v in val.items():
1021 if val['freq'] == 'weekly' and val.get('byday'):
1022 for day in val['byday'].split(','):
1026 if val.get('until'):
1027 until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
1028 new_val['end_date'] = until.strftime('%Y-%m-%d')
1030 new_val.pop('until')
1032 if val.get('bymonthday'):
1033 new_val['day'] = val.get('bymonthday')
1034 val.pop('bymonthday')
1035 new_val['select1'] = 'date'
1036 new_val.pop('bymonthday')
1038 if val.get('byday'):
1039 d = val.get('byday')
1041 new_val['byday'] = d[:2]
1042 new_val['week_list'] = d[2:4].upper()
1044 new_val['byday'] = d[:1]
1045 new_val['week_list'] = d[1:3].upper()
1046 new_val['select1'] = 'day'
1048 if val.get('bymonth'):
1049 new_val['month_list'] = val.get('bymonth')
1051 new_val.pop('bymonth')
1053 for k, v in new_val.items():
1054 qry += ", %s=%%s" % k
1057 qry = qry + " where id=%s"
1059 cr.execute(qry, qry_args)
1062 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
1064 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
1065 @param self: The object pointer
1066 @param cr: the current row, from the database cursor,
1067 @param id: List of calendar event's ids.
1068 @param context: A standard dictionary for contextual values
1069 @return: dictionary of rrule value.
1073 for datas in self.read(cr, uid, ids, context=context):
1075 if datas.get('rrule_type'):
1076 if datas.get('rrule_type') == 'none':
1077 result[event] = False
1078 cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
1079 if datas.get('rrule_type') :
1080 if datas.get('interval', 0) < 0:
1081 raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
1082 if datas.get('count', 0) < 0:
1083 raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1084 rrule_custom = self.compute_rule_string(cr, uid, datas, \
1086 result[event] = rrule_custom
1088 result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
1090 for id, myrule in result.items():
1091 #Remove the events generated from recurrent event
1093 self.unlink_events(cr, uid, [id], context=context)
1094 print 'rrule creation', result
1098 'id': fields.integer('ID'),
1099 'sequence': fields.integer('Sequence'),
1100 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1101 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1102 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1103 'create_date': fields.datetime('Created', readonly=True),
1104 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1105 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1106 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1107 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1108 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1109 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1110 'Show as', states={'done': [('readonly', True)]}),
1111 'base_calendar_url': fields.char('Caldav URL', size=264),
1112 'state': fields.selection([('tentative', 'Tentative'),
1113 ('confirmed', 'Confirmed'),
1114 ('cancelled', 'Cancelled')], 'State', readonly=True),
1115 'exdate': fields.text('Exception Date/Times', help="This property \
1116 defines the list of date/time exceptions for a recurring calendar component."),
1117 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1118 rule or repeating pattern of time to exclude from the recurring rule."),
1119 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1120 string='Recurrent Rule', store=True, \
1121 fnct_inv=_set_rrulestring, help='Defines a\
1122 rule or repeating pattern for recurring events\n\
1123 e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
1124 FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
1125 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1126 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1127 ('yearly', 'Yearly'),],
1128 'Recurrency', states={'done': [('readonly', True)]},
1129 help="Let the event automatically repeat at that interval"),
1130 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1131 help="Set an alarm at this time, before the event occurs" ),
1132 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1133 'recurrent_uid': fields.integer('Recurrent ID'),
1134 'recurrent_id': fields.datetime('Recurrent ID date'),
1135 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1136 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1137 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1138 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1139 'freq': fields.selection([('None', 'No Repeat'),
1140 ('hourly', 'Hours'),
1142 ('weekly', 'Weeks'),
1143 ('monthly', 'Months'),
1144 ('yearly', 'Years'), ], 'Frequency'),
1146 'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1147 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1148 'count': fields.integer('Repeat', help="Repeat x times"),
1149 'mo': fields.boolean('Mon'),
1150 'tu': fields.boolean('Tue'),
1151 'we': fields.boolean('Wed'),
1152 'th': fields.boolean('Thu'),
1153 'fr': fields.boolean('Fri'),
1154 'sa': fields.boolean('Sat'),
1155 'su': fields.boolean('Sun'),
1156 'select1': fields.selection([('date', 'Date of month'),
1157 ('day', 'Day of month')], 'Option'),
1158 'day': fields.integer('Date of month'),
1159 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1160 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1161 ('FR', 'Friday'), ('SA', 'Saturday'), \
1162 ('SU', 'Sunday')], 'Weekday'),
1163 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1164 ('3', 'Third'), ('4', 'Fourth'), \
1165 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1166 'month_list': fields.selection(months.items(), 'Month'),
1167 'end_date': fields.date('Repeat Until'),
1168 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1169 'event_id', 'attendee_id', 'Attendees'),
1170 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1171 'active': fields.boolean('Active', help="If the active field is set to \
1172 true, it will allow you to hide the event alarm information without removing it."),
1173 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1174 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1176 def default_organizer(self, cr, uid, context=None):
1177 user_pool = self.pool.get('res.users')
1178 user = user_pool.browse(cr, uid, uid, context=context)
1181 res += " <%s>" %(user.user_email)
1185 'end_type' : 'forever',
1186 'state': 'tentative',
1193 'user_id': lambda self, cr, uid, ctx: uid,
1194 'organizer': default_organizer,
1198 def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
1203 if edit_all and rrule_type:
1205 base_calendar_id2real_id(id)
1208 def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1210 Modifies the recurring event
1211 @param cr: the current row, from the database cursor,
1212 @param uid: the current user’s ID for security checks,
1213 @param event_ids: List of crm meeting’s IDs.
1214 @param context: A standard dictionary for contextual values
1217 for event_id in event_ids:
1218 event_id = base_calendar_id2real_id(event_id)
1221 defaults.update({'table': self._table})
1223 qry = "UPDATE %(table)s set name = '%(name)s', \
1224 date = '%(date)s', date_deadline = '%(date_deadline)s'"
1225 if defaults.get('alarm_id'):
1226 qry += ", alarm_id = %(alarm_id)s"
1227 if defaults.get('location'):
1228 qry += ", location = '%(location)s'"
1229 qry += "WHERE id = %s" % (event_id)
1230 cr.execute(qry, defaults)
1234 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
1235 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1236 This method gives ids of dates that comes between start date and end date of calendar views
1237 @param self: The object pointer
1238 @param cr: the current row, from the database cursor,
1239 @param uid: the current user’s ID for security checks,
1240 @param base_start_date: Get Start Date
1241 @param base_until_date: Get End Date
1242 @param limit: The Number of Results to Return """
1246 limit = limit == 0 and 100 or limit
1247 virtual_id = context and context.get('virtual_id', False) or False
1248 print 'context', context
1250 if isinstance(select, (str, int, long)):
1256 if ids and virtual_id:
1257 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1258 m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1259 " m where m.id = ANY(%s)", (ids,) )
1263 for data in cr.dictfetchall():
1265 print 'count', count
1266 print 'limit', limit
1267 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1268 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1272 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1273 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1274 start_date = event_date
1275 if not data['rrule']:
1276 if start_date and (event_date < start_date):
1278 if until_date and (event_date > until_date):
1281 if not data['recurrent_id']:
1282 result.append(idval)
1285 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1286 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1287 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1288 if ls[1] == data['recurrent_id']:
1289 result.append(idval)
1290 recur_dict.append(ex_id)
1292 print 'rrule', data['rrule'], data['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)
1324 print "count", count
1329 print "result", result
1331 ids = list(set(result)-set(recur_dict))
1332 if isinstance(select, (str, int, long)):
1333 return ids and ids[0] or False
1334 print "return ids", ids
1337 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1339 Compute rule string according to value type RECUR of iCalendar from the values given.
1340 @param self: the object pointer
1341 @param cr: the current row, from the database cursor,
1342 @param uid: the current user’s ID for security checks,
1343 @param datas: dictionary of freq and interval value.
1344 @param context: A standard dictionary for contextual values
1345 @return: String value of the format RECUR of iCalendar
1348 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1352 freq=datas.get('rrule_type')
1356 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1358 if freq == 'weekly':
1359 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1361 weekstring = ';BYDAY=' + ','.join(byday)
1363 elif freq == 'monthly':
1364 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1365 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1366 if datas.get('select1')=='day':
1367 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1368 elif datas.get('select1')=='date':
1369 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1372 if datas.get('end_date'):
1373 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1374 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1375 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1377 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1378 + enddate + monthstring + yearstring
1382 def search(self, cr, uid, args, offset=0, limit=100, order=None,
1383 context=None, count=False):
1385 args_without_date = []
1390 if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1391 args_without_date.append(arg)
1393 if arg[1] in ('>', '>='):
1397 elif arg[1] in ('<', '<='):
1401 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1402 offset, limit, order, context, count=False)
1403 print "result normaux", res
1404 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
1405 print "result de merde", res
1406 print 'len', len(res)
1407 return count and len(res) or res
1410 def get_edit_all(self, cr, uid, id, vals=None):
1412 return true if we have to edit all meeting from the same recurrent
1413 or only on occurency
1415 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1416 if(vals and 'edit_all' in vals): #we jsut check edit_all
1417 return vals['edit_all']
1418 else: #it's a recurrent event and edit_all is already check
1419 return meeting['recurrency'] and meeting['edit_all']
1424 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1426 Overrides orm write method.
1427 @param self: the object pointer
1428 @param cr: the current row, from the database cursor,
1429 @param uid: the current user’s ID for security checks,
1430 @param ids: List of crm meeting's ids
1431 @param vals: Dictionary of field value.
1432 @param context: A standard dictionary for contextual values
1437 if isinstance(ids, (str, int, long)):
1443 for event_id in select:
1444 real_event_id = base_calendar_id2real_id(event_id)
1447 if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1448 event_id = real_event_id
1451 if len(str(event_id).split('-')) > 1:
1452 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1453 'rrule', 'duration', 'exdate'])
1454 if data.get('rrule'):
1457 'recurrent_uid': real_event_id,
1458 'recurrent_id': data.get('date'),
1459 'rrule_type': 'none',
1462 'recurrency' : False,
1465 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1467 date_new = event_id.split('-')[1]
1468 date_new = time.strftime("%Y%m%dT%H%M%S", \
1469 time.strptime(date_new, "%Y%m%d%H%M%S"))
1470 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1471 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1473 context.update({'active_id': new_id, 'active_ids': [new_id]})
1475 if not real_event_id in new_ids:
1476 new_ids.append(real_event_id)
1478 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1479 vals['vtimezone'] = vals['vtimezone'][40:]
1481 updated_vals = self.onchange_dates(cr, uid, new_ids,
1482 vals.get('date', False),
1483 vals.get('duration', False),
1484 vals.get('date_deadline', False),
1485 vals.get('allday', False),
1487 vals.update(updated_vals.get('value', {}))
1489 if not 'edit_all' in vals:
1490 vals['edit_all'] = False
1493 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1495 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1496 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1497 # change alarm details
1498 alarm_obj = self.pool.get('res.alarm')
1499 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1502 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1504 Overrides orm browse method.
1505 @param self: the object pointer
1506 @param cr: the current row, from the database cursor,
1507 @param uid: the current user’s ID for security checks,
1508 @param ids: List of crm meeting's ids
1509 @param context: A standard dictionary for contextual values
1510 @return: the object list.
1512 if isinstance(ids, (str, int, long)):
1516 select = map(lambda x: base_calendar_id2real_id(x), select)
1517 res = super(calendar_event, self).browse(cr, uid, select, context, \
1518 list_class, fields_process)
1519 if isinstance(ids, (str, int, long)):
1520 return res and res[0] or False
1524 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1526 Overrides orm Read method.Read List of fields for calendar event.
1527 @param cr: the current row, from the database cursor,
1528 @param user: the current user’s ID for security checks,
1529 @param ids: List of calendar event's id.
1530 @param fields: List of fields.
1531 @param context: A standard dictionary for contextual values
1532 @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1534 # FIXME This whole id mangling has to go!
1538 print 'read ids', ids
1541 if isinstance(ids, (str, int, long)):
1545 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1546 print 'selected', select
1548 if fields and 'date' not in fields:
1549 fields.append('date')
1550 if fields and 'duration' not in fields:
1551 fields.append('duration')
1554 for base_calendar_id, real_id in select:
1555 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1556 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1560 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1561 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1563 res['date_deadline'] = ls[2]
1564 res['id'] = base_calendar_id
1567 if isinstance(ids, (str, int, long)):
1568 return result and result[0] or False
1570 print 'resultat', result
1573 def copy(self, cr, uid, id, default=None, context=None):
1575 Duplicate record on specified id.
1576 @param self: the object pointer.
1577 @param cr: the current row, from the database cursor,
1578 @param id: id of record from which we duplicated.
1579 @param context: A standard dictionary for contextual values
1580 @return: Duplicate record id.
1584 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1585 alarm_obj = self.pool.get('res.alarm')
1586 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1590 def unlink(self, cr, uid, ids, context=None):
1592 Deletes records specified in ids.
1593 @param self: the object pointer.
1594 @param cr: the current row, from the database cursor,
1595 @param id: List of calendar event's id.
1596 @param context: A standard dictionary for contextual values
1600 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1601 event_id = event_datas['id']
1603 if self.get_edit_all(cr, uid, event_id, vals=None):
1604 event_id = base_calendar_id2real_id(event_id)
1606 if isinstance(event_id, (int, long)):
1607 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1608 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1609 self.unlink_events(cr, uid, [event_id], context=context)
1611 str_event, date_new = event_id.split('-')
1612 event_id = int(str_event)
1613 if event_datas['rrule']:
1614 # Remove one of the recurrent event
1615 date_new = time.strftime("%Y%m%dT%H%M%S", \
1616 time.strptime(date_new, "%Y%m%d%H%M%S"))
1617 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1618 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1620 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1621 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1622 self.unlink_events(cr, uid, [event_id], context=context)
1625 def create(self, cr, uid, vals, context=None):
1628 @param self: the object pointer
1629 @param cr: the current row, from the database cursor,
1630 @param uid: the current user’s ID for security checks,
1631 @param vals: dictionary of every field value.
1632 @param context: A standard dictionary for contextual values
1633 @return: new created record id.
1638 virtual_id = context.get('virtual_id', False)
1640 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1641 vals['vtimezone'] = vals['vtimezone'][40:]
1643 updated_vals = self.onchange_dates(cr, uid, [],
1644 vals.get('date', False),
1645 vals.get('duration', False),
1646 vals.get('date_deadline', False),
1647 vals.get('allday', False),
1649 vals.update(updated_vals.get('value', {}))
1651 res = super(calendar_event, self).create(cr, uid, vals, context)
1652 alarm_obj = self.pool.get('res.alarm')
1653 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1657 if vals.get('rrule_type') != 'none' and virtual_id:
1658 res = real_id2base_calendar_id(res, vals.get('date', False))
1662 def do_tentative(self, cr, uid, ids, context=None, *args):
1663 """ Makes event invitation as Tentative
1664 @param self: The object pointer
1665 @param cr: the current row, from the database cursor,
1666 @param uid: the current user’s ID for security checks,
1667 @param ids: List of Event IDs
1668 @param *args: Get Tupple value
1669 @param context: A standard dictionary for contextual values
1671 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1673 def do_cancel(self, cr, uid, ids, context=None, *args):
1674 """ Makes event invitation as Tentative
1675 @param self: The object pointer
1676 @param cr: the current row, from the database cursor,
1677 @param uid: the current user’s ID for security checks,
1678 @param ids: List of Event IDs
1679 @param *args: Get Tupple value
1680 @param context: A standard dictionary for contextual values
1682 return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1684 def do_confirm(self, cr, uid, ids, context=None, *args):
1685 """ Makes event invitation as Tentative
1686 @param self: The object pointer
1687 @param cr: the current row, from the database cursor,
1688 @param uid: the current user’s ID for security checks,
1689 @param ids: List of Event IDs
1690 @param *args: Get Tupple value
1691 @param context: A standard dictionary for contextual values
1693 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1697 class calendar_todo(osv.osv):
1698 """ Calendar Task """
1700 _name = "calendar.todo"
1701 _inherit = "calendar.event"
1702 _description = "Calendar Task"
1704 def _get_date(self, cr, uid, ids, name, arg, context=None):
1707 @param self: The object pointer
1708 @param cr: the current row, from the database cursor,
1709 @param uid: the current user’s ID for security checks,
1710 @param ids: List of calendar todo's IDs.
1711 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1712 @param context: A standard dictionary for contextual values
1716 for event in self.browse(cr, uid, ids, context=context):
1717 res[event.id] = event.date_start
1720 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1723 @param self: The object pointer
1724 @param cr: the current row, from the database cursor,
1725 @param uid: the current user’s ID for security checks,
1726 @param id: calendar's ID.
1727 @param value: Get Value
1728 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1729 @param context: A standard dictionary for contextual values
1732 assert name == 'date'
1733 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1736 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1737 string='Duration', store=True, type='datetime'),
1738 'duration': fields.integer('Duration'),
1746 class ir_attachment(osv.osv):
1747 _name = 'ir.attachment'
1748 _inherit = 'ir.attachment'
1750 def search_count(self, cr, user, args, context=None):
1752 @param self: The object pointer
1753 @param cr: the current row, from the database cursor,
1754 @param user: the current user’s ID for security checks,
1755 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1756 @param context: A standard dictionary for contextual values
1761 args1.append(map(lambda x:str(x).split('-')[0], arg))
1762 return super(ir_attachment, self).search_count(cr, user, args1, context)
1766 def create(self, cr, uid, vals, context=None):
1768 id = context.get('default_res_id', False)
1769 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1770 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1772 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1773 context=None, count=False):
1775 @param self: The object pointer
1776 @param cr: the current row, from the database cursor,
1777 @param uid: the current user’s ID for security checks,
1778 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1779 @param offset: The Number of Results to pass,
1780 @param limit: The Number of Results to Return,
1781 @param context: A standard dictionary for contextual values
1785 for i, arg in enumerate(new_args):
1786 if arg[0] == 'res_id':
1787 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1789 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1790 limit=limit, order=order, context=context, count=False)
1793 class ir_values(osv.osv):
1794 _inherit = 'ir.values'
1796 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1797 isobject=False, meta=False, preserve_user=False, company=False):
1800 @param self: The object pointer
1801 @param cr: the current row, from the database cursor,
1802 @param uid: the current user’s ID for security checks,
1803 @param model: Get The Model
1808 if type(data) in (list, tuple):
1809 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1811 new_model.append(data)
1812 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1813 value, replace, isobject, meta, preserve_user, company)
1815 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1816 res_id_req=False, without_user=True, key2_req=True):
1819 @param self: The object pointer
1820 @param cr: the current row, from the database cursor,
1821 @param uid: the current user’s ID for security checks,
1822 @param model: Get The Model
1828 if type(data) in (list, tuple):
1829 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1831 new_model.append(data)
1832 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1833 meta, context, res_id_req, without_user, key2_req)
1837 class ir_model(osv.osv):
1839 _inherit = 'ir.model'
1841 def read(self, cr, uid, ids, fields=None, context=None,
1842 load='_classic_read'):
1844 Overrides orm read method.
1845 @param self: The object pointer
1846 @param cr: the current row, from the database cursor,
1847 @param uid: the current user’s ID for security checks,
1848 @param ids: List of IR Model’s IDs.
1849 @param context: A standard dictionary for contextual values
1851 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1854 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1855 context=context, load=load)
1858 val['id'] = base_calendar_id2real_id(val['id'])
1859 return isinstance(ids, (str, int, long)) and data[0] or data
1863 class virtual_report_spool(web_services.report_spool):
1865 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1868 @param self: The object pointer
1869 @param db: get the current database,
1870 @param uid: the current user’s ID for security checks,
1871 @param context: A standard dictionary for contextual values
1874 if object == 'printscreen.list':
1875 return super(virtual_report_spool, self).exp_report(db, uid, \
1876 object, ids, datas, context)
1879 new_ids.append(base_calendar_id2real_id(id))
1880 if datas.get('id', False):
1881 datas['id'] = base_calendar_id2real_id(datas['id'])
1882 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1884 virtual_report_spool()
1886 class res_users(osv.osv):
1887 _inherit = 'res.users'
1889 def _get_user_avail(self, cr, uid, ids, context=None):
1891 Get User Availability
1892 @param self: The object pointer
1893 @param cr: the current row, from the database cursor,
1894 @param uid: the current user’s ID for security checks,
1895 @param ids: List of res user’s IDs.
1896 @param context: A standard dictionary for contextual values
1899 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1901 attendee_obj = self.pool.get('calendar.attendee')
1902 attendee_ids = attendee_obj.search(cr, uid, [
1903 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1904 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1907 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1908 user_id = attendee_data['user_id']
1910 res.update({user_id:status})
1912 #TOCHECK: Delegated Event
1914 if user_id not in res:
1915 res[user_id] = 'free'
1919 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1921 Get User Availability Function
1922 @param self: The object pointer
1923 @param cr: the current row, from the database cursor,
1924 @param uid: the current user’s ID for security checks,
1925 @param ids: List of res user’s IDs.
1926 @param context: A standard dictionary for contextual values
1929 return self._get_user_avail(cr, uid, ids, context=context)
1932 'availability': fields.function(_get_user_avail_fun, type='selection', \
1933 selection=[('free', 'Free'), ('busy', 'Busy')], \
1934 string='Free/Busy', method=True),
1940 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: