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 dateutil.relativedelta import relativedelta
26 from osv import fields, osv
27 from service import web_services
28 from tools.translate import _
35 1: "January", 2: "February", 3: "March", 4: "April", \
36 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
37 10: "October", 11: "November", 12: "December"
40 def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
42 Get recurrent dates based on Rule string considering exdate and start date
43 @param rrulestring: Rulestring
44 @param exdate: List of exception dates for rrule
45 @param startdate: Startdate for computing recurrent dates
46 @return: List of Recurrent dates
49 val = parser.parse(''.join((re.compile('\d')).findall(date)))
55 startdate = datetime.now()
59 rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
63 datetime_obj = todate(date)
64 rset1._exdate.append(datetime_obj)
67 rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
72 print "len de liste" ,len(l)
75 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
77 This function converts virtual event id into real id of actual event
78 @param base_calendar_id: Id of calendar
79 @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
82 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
83 res = base_calendar_id.split('-')
88 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
89 time.strptime(res[1], "%Y%m%d%H%M%S"))
90 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
91 end = start + timedelta(hours=with_date)
92 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
95 return base_calendar_id and int(base_calendar_id) or base_calendar_id
97 def real_id2base_calendar_id(real_id, recurrent_date):
99 Convert real id of record into virtual id using recurrent_date
100 e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
102 @return: real id with recurrent date.
105 if real_id and recurrent_date:
106 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
107 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
108 return '%d-%s' % (real_id, recurrent_date)
111 def _links_get(self, cr, uid, context=None):
114 @param cr: the current row, from the database cursor,
115 @param uid: the current user’s ID for security checks,
116 @param context: A standard dictionary for contextual values
117 @return: list of dictionary which contain object and name and id.
119 obj = self.pool.get('res.request.link')
120 ids = obj.search(cr, uid, [])
121 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
122 return [(r['object'], r['name']) for r in res]
124 html_invitation = """
127 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
128 <title>%(name)s</title>
131 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
132 style="font-family: Arial, Sans-serif; font-size: 14">
134 <td width="100%%">Hello,</td>
137 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
140 <td width="100%%">Below are the details of event:</td>
144 <table cellspacing="0" cellpadding="5" border="0" summary=""
145 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
146 <tr valign="center" align="center">
147 <td bgcolor="DFDFDF">
153 <table cellpadding="8" cellspacing="0" border="0"
154 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
158 <div><b>Start Date</b></div>
161 <td>%(start_date)s</td>
163 <div><b>End Date</b></div>
166 <td width="25%%">%(end_date)s</td>
169 <td><b>Description</b></td>
171 <td colspan="3">%(description)s</td>
175 <div><b>Location</b></div>
178 <td colspan="3">%(location)s</td>
182 <div><b>Event Attendees</b></div>
187 <div>%(attendees)s</div>
195 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
196 style="font-family: Arial, Sans-serif; font-size: 14">
198 <td width="100%%">From:</td>
201 <td width="100%%">%(user)s</td>
204 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
207 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
214 class calendar_attendee(osv.osv):
216 Calendar Attendee Information
218 _name = 'calendar.attendee'
219 _description = 'Attendee information'
224 def _get_address(self, name=None, email=None):
226 Gives email information in ical CAL-ADDRESS type format
227 @param name: Name for CAL-ADDRESS value
228 @param email: Email address for CAL-ADDRESS value
232 return (name or '') + (email and ('MAILTO:' + email) or '')
234 def _compute_data(self, cr, uid, ids, name, arg, context=None):
236 Compute data on function fields for attendee values .
237 @param cr: the current row, from the database cursor,
238 @param uid: the current user’s ID for security checks,
239 @param ids: List of calendar attendee’s IDs.
240 @param name: name of field.
241 @param context: A standard dictionary for contextual values
242 @return: Dictionary of form {id: {'field Name': value'}}.
246 for attdata in self.browse(cr, uid, ids, context=context):
249 if name == 'sent_by':
250 if not attdata.sent_by_uid:
251 result[id][name] = ''
254 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
255 attdata.sent_by_uid.address_id.email)
259 result[id][name] = attdata.user_id.name
260 elif attdata.partner_address_id:
261 result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
263 result[id][name] = attdata.email or ''
265 if name == 'delegated_to':
267 for child in attdata.child_ids:
269 todata.append('MAILTO:' + child.email)
270 result[id][name] = ', '.join(todata)
272 if name == 'delegated_from':
274 for parent in attdata.parent_ids:
276 fromdata.append('MAILTO:' + parent.email)
277 result[id][name] = ', '.join(fromdata)
279 if name == 'event_date':
281 result[id][name] = attdata.ref.date
283 result[id][name] = False
285 if name == 'event_end_date':
287 result[id][name] = attdata.ref.date_deadline
289 result[id][name] = False
291 if name == 'sent_by_uid':
293 result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
295 result[id][name] = uid
297 if name == 'language':
298 user_obj = self.pool.get('res.users')
299 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
300 result[id][name] = lang.replace('_', '-')
304 def _links_get(self, cr, uid, context=None):
306 Get request link for ref field in calendar attendee.
307 @param cr: the current row, from the database cursor,
308 @param uid: the current user’s ID for security checks,
309 @param context: A standard dictionary for contextual values
310 @return: list of dictionary which contain object and name and id.
312 obj = self.pool.get('res.request.link')
313 ids = obj.search(cr, uid, [])
314 res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
315 return [(r['object'], r['name']) for r in res]
317 def _lang_get(self, cr, uid, context=None):
319 Get language for language selection field.
320 @param cr: the current row, from the database cursor,
321 @param uid: the current user’s ID for security checks,
322 @param context: A standard dictionary for contextual values
323 @return: list of dictionary which contain code and name and id.
325 obj = self.pool.get('res.lang')
326 ids = obj.search(cr, uid, [])
327 res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
328 res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
332 'cutype': fields.selection([('individual', 'Individual'), \
333 ('group', 'Group'), ('resource', 'Resource'), \
334 ('room', 'Room'), ('unknown', '') ], \
335 'Invite Type', help="Specify the type of Invitation"),
336 'member': fields.char('Member', size=124,
337 help="Indicate the groups that the attendee belongs to"),
338 'role': fields.selection([('req-participant', 'Participation required'), \
339 ('chair', 'Chair Person'), \
340 ('opt-participant', 'Optional Participation'), \
341 ('non-participant', 'For information Purpose')], 'Role', \
342 help='Participation role for the calendar user'),
343 'state': fields.selection([('tentative', 'Tentative'),
344 ('needs-action', 'Needs Action'),
345 ('accepted', 'Accepted'),
346 ('declined', 'Declined'),
347 ('delegated', 'Delegated')], 'State', readonly=True, \
348 help="Status of the attendee's participation"),
349 'rsvp': fields.boolean('Required Reply?',
350 help="Indicats whether the favor of a reply is requested"),
351 'delegated_to': fields.function(_compute_data, method=True, \
352 string='Delegated To', type="char", size=124, store=True, \
353 multi='delegated_to', help="The users that the original \
354 request was delegated to"),
355 'delegated_from': fields.function(_compute_data, method=True, string=\
356 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
357 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
358 'attendee_id', 'parent_id', 'Delegrated From'),
359 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
360 'attendee_id', 'child_id', 'Delegrated To'),
361 'sent_by': fields.function(_compute_data, method=True, string='Sent By', \
362 type="char", multi='sent_by', store=True, size=124, \
363 help="Specify the user that is acting on behalf of the calendar user"),
364 'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', \
365 type="many2one", relation="res.users", multi='sent_by_uid'),
366 'cn': fields.function(_compute_data, method=True, string='Common name', \
367 type="char", size=124, multi='cn', store=True),
368 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
369 that points to the directory information corresponding to the attendee."),
370 'language': fields.function(_compute_data, method=True, string='Language', \
371 type="selection", selection=_lang_get, multi='language', \
372 store=True, help="To specify the language for text values in a\
373 property or property parameter."),
374 'user_id': fields.many2one('res.users', 'User'),
375 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
376 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
377 relation='res.partner', string='Partner', help="Partner related to contact"),
378 'email': fields.char('Email', size=124, help="Email of Invited Person"),
379 'event_date': fields.function(_compute_data, method=True, string='Event Date', \
380 type="datetime", multi='event_date'),
381 'event_end_date': fields.function(_compute_data, method=True, \
382 string='Event End Date', type="datetime", \
383 multi='event_end_date'),
384 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
385 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
389 'state': 'needs-action',
390 'role': 'req-participant',
392 'cutype': 'individual',
395 def copy(self, cr, uid, id, default=None, context=None):
396 raise osv.except_osv(_('Warning!'), _('Can not Duplicate'))
398 def get_ics_file(self, cr, uid, event_obj, context=None):
400 Returns iCalendar file for the event invitation
401 @param self: The object pointer
402 @param cr: the current row, from the database cursor,
403 @param uid: the current user’s ID for security checks,
404 @param event_obj: Event object (browse record)
405 @param context: A standard dictionary for contextual values
406 @return: .ics file content
409 def ics_datetime(idate, short=False):
411 if short or len(idate)<=10:
412 return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
414 return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
418 # FIXME: why isn't this in CalDAV?
422 cal = vobject.iCalendar()
423 event = cal.add('vevent')
424 if not event_obj.date_deadline or not event_obj.date:
425 raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
426 event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
427 event.add('dtstart').value = ics_datetime(event_obj.date)
428 event.add('dtend').value = ics_datetime(event_obj.date_deadline)
429 event.add('summary').value = event_obj.name
430 if event_obj.description:
431 event.add('description').value = event_obj.description
432 if event_obj.location:
433 event.add('location').value = event_obj.location
435 event.add('rrule').value = event_obj.rrule
436 if event_obj.organizer:
437 event_org = event.add('organizer')
438 event_org.params['CN'] = [event_obj.organizer]
439 event_org.value = 'MAILTO:' + (event_obj.organizer)
440 elif event_obj.user_id or event_obj.organizer_id:
441 event_org = event.add('organizer')
442 organizer = event_obj.organizer_id
444 organizer = event_obj.user_id
445 event_org.params['CN'] = [organizer.name]
446 event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
448 if event_obj.alarm_id:
449 # computes alarm data
450 valarm = event.add('valarm')
451 alarm_object = self.pool.get('res.alarm')
452 alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
453 # Compute trigger data
454 interval = alarm_data['trigger_interval']
455 occurs = alarm_data['trigger_occurs']
456 duration = (occurs == 'after' and alarm_data['trigger_duration']) \
457 or -(alarm_data['trigger_duration'])
458 related = alarm_data['trigger_related']
459 trigger = valarm.add('TRIGGER')
460 trigger.params['related'] = [related.upper()]
461 if interval == 'days':
462 delta = timedelta(days=duration)
463 if interval == 'hours':
464 delta = timedelta(hours=duration)
465 if interval == 'minutes':
466 delta = timedelta(minutes=duration)
467 trigger.value = delta
468 # Compute other details
469 valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
471 for attendee in event_obj.attendee_ids:
472 attendee_add = event.add('attendee')
473 attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
474 attendee_add.params['ROLE'] = [str(attendee.role)]
475 attendee_add.params['RSVP'] = [str(attendee.rsvp)]
476 attendee_add.value = 'MAILTO:' + (attendee.email or '')
477 res = cal.serialize()
480 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
482 Send mail for event invitation to event attendees.
483 @param cr: the current row, from the database cursor,
484 @param uid: the current user’s ID for security checks,
485 @param ids: List of attendee’s IDs.
486 @param email_from: Email address for user sending the mail
487 @param context: A standard dictionary for contextual values
493 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
494 for att in self.browse(cr, uid, ids, context=context):
495 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
496 sign = '<br>'.join(sign and sign.split('\n') or [])
501 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
503 for att2 in self.browse(cr, uid, other_invitation_ids):
504 att_infos.append(((att2.user_id and att2.user_id.name) or \
505 (att2.partner_id and att2.partner_id.name) or \
506 att2.email) + ' - Status: ' + att2.state.title())
507 body_vals = {'name': res_obj.name,
508 'start_date': res_obj.date,
509 'end_date': res_obj.date_deadline or False,
510 'description': res_obj.description or '-',
511 'location': res_obj.location or '-',
512 'attendees': '<br>'.join(att_infos),
513 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
517 body = html_invitation % body_vals
518 if mail_to and email_from:
519 attach = self.get_ics_file(cr, uid, res_obj, context=context)
525 attach=attach and [('invitation.ics', attach)] or None,
531 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
533 Make entry on email and availbility on change of user_id field.
534 @param cr: the current row, from the database cursor,
535 @param uid: the current user’s ID for security checks,
536 @param ids: List of calendar attendee’s IDs.
537 @param user_id: Changed value of User id
538 @return: dictionary of value. which put value in email and availability fields.
542 return {'value': {'email': ''}}
543 usr_obj = self.pool.get('res.users')
544 user = usr_obj.browse(cr, uid, user_id, *args)
545 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
547 def do_tentative(self, cr, uid, ids, context=None, *args):
548 """ Makes event invitation as Tentative
549 @param self: The object pointer
550 @param cr: the current row, from the database cursor,
551 @param uid: the current user’s ID for security checks,
552 @param ids: List of calendar attendee’s IDs
553 @param *args: Get Tupple value
554 @param context: A standard dictionary for contextual values
556 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
558 def do_accept(self, cr, uid, ids, context=None, *args):
560 Update state of invitation as Accepted and
561 if the invited user is other then event user it will make a copy of this event for invited user
562 @param cr: the current row, from the database cursor,
563 @param uid: the current user’s ID for security checks,
564 @param ids: List of calendar attendee’s IDs.
565 @param context: A standard dictionary for contextual values
571 for vals in self.browse(cr, uid, ids, context=context):
572 if vals.ref and vals.ref.user_id:
573 mod_obj = self.pool.get(vals.ref._name)
574 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
575 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
576 self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
580 def do_decline(self, cr, uid, ids, context=None, *args):
581 """ Marks event invitation as Declined
582 @param self: The object pointer
583 @param cr: the current row, from the database cursor,
584 @param uid: the current user’s ID for security checks,
585 @param ids: List of calendar attendee’s IDs
586 @param *args: Get Tupple value
587 @param context: A standard dictionary for contextual values """
590 return self.write(cr, uid, ids, {'state': 'declined'}, context)
592 def create(self, cr, uid, vals, context=None):
593 """ Overrides orm create method.
594 @param self: The object pointer
595 @param cr: the current row, from the database cursor,
596 @param uid: the current user’s ID for security checks,
597 @param vals: Get Values
598 @param context: A standard dictionary for contextual values """
602 if not vals.get("email") and vals.get("cn"):
603 cnval = vals.get("cn").split(':')
604 email = filter(lambda x:x.__contains__('@'), cnval)
605 vals['email'] = email and email[0] or ''
606 vals['cn'] = vals.get("cn")
607 res = super(calendar_attendee, self).create(cr, uid, vals, context)
611 class res_alarm(osv.osv):
612 """Resource Alarm """
614 _description = 'Basic Alarm Information'
617 'name':fields.char('Name', size=256, required=True),
618 'trigger_occurs': fields.selection([('before', 'Before'), \
619 ('after', 'After')], \
620 'Triggers', required=True),
621 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
622 ('hours', 'Hours'), \
623 ('days', 'Days')], 'Interval', \
625 'trigger_duration': fields.integer('Duration', required=True),
626 'trigger_related': fields.selection([('start', 'The event starts'), \
627 ('end', 'The event ends')], \
628 'Related to', required=True),
629 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
630 are both optional, but if one occurs, so MUST the other"""),
631 'repeat': fields.integer('Repeat'),
632 'active': fields.boolean('Active', help="If the active field is set to \
633 true, it will allow you to hide the event alarm information without removing it.")
636 'trigger_interval': 'minutes',
637 'trigger_duration': 5,
638 'trigger_occurs': 'before',
639 'trigger_related': 'start',
643 def do_alarm_create(self, cr, uid, ids, model, date, context=None):
645 Create Alarm for event.
646 @param cr: the current row, from the database cursor,
647 @param uid: the current user’s ID for security checks,
648 @param ids: List of res alarm’s IDs.
649 @param model: Model name.
650 @param date: Event date
651 @param context: A standard dictionary for contextual values
656 alarm_obj = self.pool.get('calendar.alarm')
657 res_alarm_obj = self.pool.get('res.alarm')
658 ir_obj = self.pool.get('ir.model')
659 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
661 model_obj = self.pool.get(model)
662 for data in model_obj.browse(cr, uid, ids, context=context):
664 basic_alarm = data.alarm_id
665 cal_alarm = data.base_calendar_alarm_id
666 if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
668 # Find for existing res.alarm
669 duration = cal_alarm.trigger_duration
670 interval = cal_alarm.trigger_interval
671 occurs = cal_alarm.trigger_occurs
672 related = cal_alarm.trigger_related
673 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
674 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
677 'trigger_duration': duration,
678 'trigger_interval': interval,
679 'trigger_occurs': occurs,
680 'trigger_related': related,
681 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
683 new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
685 new_res_alarm = alarm_ids[0]
686 cr.execute('UPDATE %s ' % model_obj._table + \
687 ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
689 (cal_alarm.id, new_res_alarm, data.id))
691 self.do_alarm_unlink(cr, uid, [data.id], model)
695 'description': data.description,
697 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
698 'trigger_related': basic_alarm.trigger_related,
699 'trigger_duration': basic_alarm.trigger_duration,
700 'trigger_occurs': basic_alarm.trigger_occurs,
701 'trigger_interval': basic_alarm.trigger_interval,
702 'duration': basic_alarm.duration,
703 'repeat': basic_alarm.repeat,
705 'event_date': data[date],
707 'model_id': model_id,
710 alarm_id = alarm_obj.create(cr, uid, vals)
711 cr.execute('UPDATE %s ' % model_obj._table + \
712 ' SET base_calendar_alarm_id=%s, alarm_id=%s '
714 ( alarm_id, basic_alarm.id, data.id) )
717 def do_alarm_unlink(self, cr, uid, ids, model, context=None):
719 Delete alarm specified in ids
720 @param cr: the current row, from the database cursor,
721 @param uid: the current user’s ID for security checks,
722 @param ids: List of res alarm’s IDs.
723 @param model: Model name for which alarm is to be cleared.
728 alarm_obj = self.pool.get('calendar.alarm')
729 ir_obj = self.pool.get('ir.model')
730 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
731 model_obj = self.pool.get(model)
732 for datas in model_obj.browse(cr, uid, ids, context=context):
733 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
735 alarm_obj.unlink(cr, uid, alarm_ids)
736 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
737 where id=%%s' % model_obj._table,(datas.id,))
742 class calendar_alarm(osv.osv):
743 _name = 'calendar.alarm'
744 _description = 'Event alarm information'
745 _inherit = 'res.alarm'
749 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
750 'name': fields.char('Summary', size=124, help="""Contains the text to be \
751 used as the message subject for email \
752 or contains the text to be used for display"""),
753 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
754 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
755 required=True, help="Defines the action to be invoked when an alarm is triggered"),
756 'description': fields.text('Description', help='Provides a more complete \
757 description of the calendar component, than that \
758 provided by the "SUMMARY" property'),
759 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
760 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
761 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
762 which is rendered when the alarm is triggered for audio,
763 * File which is intended to be sent as message attachments for email,
764 * Points to a procedure resource, which is invoked when\
765 the alarm is triggered for procedure."""),
766 'res_id': fields.integer('Resource ID'),
767 'model_id': fields.many2one('ir.model', 'Model'),
768 'user_id': fields.many2one('res.users', 'Owner'),
769 'event_date': fields.datetime('Event Date'),
770 'event_end_date': fields.datetime('Event End Date'),
771 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
772 'state':fields.selection([
777 ], 'State', select=True, readonly=True),
785 def create(self, cr, uid, vals, context=None):
787 Overrides orm create method.
788 @param self: The object pointer
789 @param cr: the current row, from the database cursor,
790 @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
791 @param context: A standard dictionary for contextual values
792 @return: new record id for calendar_alarm.
796 event_date = vals.get('event_date', False)
798 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
799 if vals['trigger_interval'] == 'days':
800 delta = timedelta(days=vals['trigger_duration'])
801 if vals['trigger_interval'] == 'hours':
802 delta = timedelta(hours=vals['trigger_duration'])
803 if vals['trigger_interval'] == 'minutes':
804 delta = timedelta(minutes=vals['trigger_duration'])
805 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
806 vals['trigger_date'] = trigger_date
807 res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
810 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
812 """Scheduler for event reminder
813 @param self: The object pointer
814 @param cr: the current row, from the database cursor,
815 @param uid: the current user’s ID for security checks,
816 @param ids: List of calendar alarm’s IDs.
817 @param use_new_cursor: False or the dbname
818 @param context: A standard dictionary for contextual values
820 return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
823 current_datetime = datetime.now()
824 request_obj = self.pool.get('res.request')
825 alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
829 for alarm in self.browse(cr, uid, alarm_ids, context=context):
830 next_trigger_date = None
832 model_obj = self.pool.get(alarm.model_id.model)
833 res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
837 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
838 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
840 trigger_interval = alarm.trigger_interval
841 if trigger_interval == 'days':
842 delta = timedelta(days=alarm.trigger_duration)
843 if trigger_interval == 'hours':
844 delta = timedelta(hours=alarm.trigger_duration)
845 if trigger_interval == 'minutes':
846 delta = timedelta(minutes=alarm.trigger_duration)
847 delta = alarm.trigger_occurs == 'after' and delta or -delta
849 for rdate in recurrent_dates:
850 if rdate + delta > current_datetime:
852 if rdate + delta <= current_datetime:
853 re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
854 rest_dates = recurrent_dates[len(re_dates):]
855 next_trigger_date = rest_dates and rest_dates[0] or None
858 re_dates = [alarm.trigger_date]
860 for r_date in re_dates:
861 ref = alarm.model_id.model + ',' + str(alarm.res_id)
863 # search for alreay sent requests
864 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
867 if alarm.action == 'display':
870 'act_from': alarm.user_id.id,
871 'act_to': alarm.user_id.id,
872 'body': alarm.description,
873 'trigger_date': r_date,
876 request_id = request_obj.create(cr, uid, value)
877 request_ids = [request_id]
878 for attendee in res_obj.attendee_ids:
880 value['act_to'] = attendee.user_id.id
881 request_id = request_obj.create(cr, uid, value)
882 request_ids.append(request_id)
883 request_obj.request_send(cr, uid, request_ids)
885 if alarm.action == 'email':
886 sub = '[Openobject Reminder] %s' % (alarm.name)
898 """ % (alarm.name, alarm.trigger_date, alarm.description, \
899 alarm.user_id.name, alarm.user_id.signature)
900 mail_to = [alarm.user_id.address_id.email]
901 for att in alarm.attendee_ids:
902 mail_to.append(att.user_id.address_id.email)
905 tools.config.get('email_from', False),
910 if next_trigger_date:
911 update_vals.update({'trigger_date': next_trigger_date})
913 update_vals.update({'state': 'done'})
914 self.write(cr, uid, [alarm.id], update_vals)
920 class calendar_event(osv.osv):
921 _name = "calendar.event"
922 _description = "Calendar Event"
925 def _tz_get(self, cr, uid, context=None):
926 return [(x.lower(), x) for x in pytz.all_timezones]
928 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
929 """Returns duration and/or end date based on values passed
930 @param self: The object pointer
931 @param cr: the current row, from the database cursor,
932 @param uid: the current user’s ID for security checks,
933 @param ids: List of calendar event’s IDs.
934 @param start_date: Starting date
935 @param duration: Duration between start date and end date
936 @param end_date: Ending Datee
937 @param context: A standard dictionary for contextual values
945 if not end_date and not duration:
947 value['duration'] = duration
949 if allday: # For all day event
950 value = {'duration': 24}
953 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
954 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
955 value['date'] = start_date
958 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
959 if end_date and not duration:
960 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
962 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
963 value['duration'] = round(duration, 2)
965 end = start + timedelta(hours=duration)
966 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
967 elif end_date and duration and not allday:
968 # we have both, keep them synchronized:
969 # set duration based on end_date (arbitrary decision: this avoid
970 # getting dates like 06:31:48 instead of 06:32:00)
971 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
973 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
974 value['duration'] = round(duration, 2)
976 return {'value': value}
978 def unlink_events(self, cr, uid, ids, context=None):
980 This function deletes event which are linked with the event with recurrent_uid
981 (Removes the events which refers to the same UID value)
986 cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
987 r_ids = map(lambda x: x[0], cr.fetchall())
988 self.unlink(cr, uid, r_ids, context=context)
991 def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
993 Sets values of fields that defines event recurrence from the value of rrule string
994 @param self: The object pointer
995 @param cr: the current row, from the database cursor,
996 @param id: List of calendar event's ids.
997 @param context: A standard dictionary for contextual values
998 @return: dictionary of rrule value.
1002 cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
1003 mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
1004 day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
1007 cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1010 for part in value.split(';'):
1011 if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1012 rrule_type = part.lower()[5:]
1015 rrule_type = 'custom'
1017 ans = value.split(';')
1019 val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1020 if not val.get('interval'):
1021 rrule_type = 'custom'
1022 elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1023 rrule_type = 'custom'
1025 qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1026 qry_args = [ rrule_type, ]
1027 new_val = val.copy()
1028 for k, v in val.items():
1029 if val['freq'] == 'weekly' and val.get('byday'):
1030 for day in val['byday'].split(','):
1034 if val.get('until'):
1035 until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
1036 new_val['end_date'] = until.strftime('%Y-%m-%d')
1038 new_val.pop('until')
1040 if val.get('bymonthday'):
1041 new_val['day'] = val.get('bymonthday')
1042 val.pop('bymonthday')
1043 new_val['select1'] = 'date'
1044 new_val.pop('bymonthday')
1046 if val.get('byday'):
1047 d = val.get('byday')
1049 new_val['byday'] = d[:2]
1050 new_val['week_list'] = d[2:4].upper()
1052 new_val['byday'] = d[:1]
1053 new_val['week_list'] = d[1:3].upper()
1054 new_val['select1'] = 'day'
1056 if val.get('bymonth'):
1057 new_val['month_list'] = val.get('bymonth')
1059 new_val.pop('bymonth')
1061 for k, v in new_val.items():
1062 qry += ", %s=%%s" % k
1065 qry = qry + " where id=%s"
1067 cr.execute(qry, qry_args)
1070 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
1072 Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
1073 @param self: The object pointer
1074 @param cr: the current row, from the database cursor,
1075 @param id: List of calendar event's ids.
1076 @param context: A standard dictionary for contextual values
1077 @return: dictionary of rrule value.
1081 for datas in self.read(cr, uid, ids, context=context):
1083 if datas.get('rrule_type'):
1084 if datas.get('rrule_type') == 'none':
1085 result[event] = False
1086 cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
1087 if datas.get('rrule_type') :
1088 if datas.get('interval', 0) < 0:
1089 raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
1090 if datas.get('count', 0) < 0:
1091 raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1092 rrule_custom = self.compute_rule_string(cr, uid, datas, \
1094 result[event] = rrule_custom
1096 result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
1098 for id, myrule in result.items():
1099 #Remove the events generated from recurrent event
1101 self.unlink_events(cr, uid, [id], context=context)
1105 'id': fields.integer('ID'),
1106 'sequence': fields.integer('Sequence'),
1107 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1108 'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1109 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1110 'create_date': fields.datetime('Created', readonly=True),
1111 'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1112 'description': fields.text('Description', states={'done': [('readonly', True)]}),
1113 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1114 ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1115 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1116 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1117 'Show as', states={'done': [('readonly', True)]}),
1118 'base_calendar_url': fields.char('Caldav URL', size=264),
1119 'state': fields.selection([('tentative', 'Tentative'),
1120 ('confirmed', 'Confirmed'),
1121 ('cancelled', 'Cancelled')], 'State', readonly=True),
1122 'exdate': fields.text('Exception Date/Times', help="This property \
1123 defines the list of date/time exceptions for a recurring calendar component."),
1124 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1125 rule or repeating pattern of time to exclude from the recurring rule."),
1126 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1127 string='Recurrent Rule', store=True, \
1128 fnct_inv=_set_rrulestring, help='Defines a\
1129 rule or repeating pattern for recurring events\n\
1130 e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
1131 FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
1132 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1133 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1134 ('yearly', 'Yearly'),],
1135 'Recurrency', states={'done': [('readonly', True)]},
1136 help="Let the event automatically repeat at that interval"),
1137 'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1138 help="Set an alarm at this time, before the event occurs" ),
1139 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1140 'recurrent_uid': fields.integer('Recurrent ID'),
1141 'recurrent_id': fields.datetime('Recurrent ID date'),
1142 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1143 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1144 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1145 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1146 'freq': fields.selection([('None', 'No Repeat'),
1147 ('hourly', 'Hours'),
1149 ('weekly', 'Weeks'),
1150 ('monthly', 'Months'),
1151 ('yearly', 'Years'), ], 'Frequency'),
1153 'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1154 'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1155 'count': fields.integer('Repeat', help="Repeat x times"),
1156 'mo': fields.boolean('Mon'),
1157 'tu': fields.boolean('Tue'),
1158 'we': fields.boolean('Wed'),
1159 'th': fields.boolean('Thu'),
1160 'fr': fields.boolean('Fri'),
1161 'sa': fields.boolean('Sat'),
1162 'su': fields.boolean('Sun'),
1163 'select1': fields.selection([('date', 'Date of month'),
1164 ('day', 'Day of month')], 'Option'),
1165 'day': fields.integer('Date of month'),
1166 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1167 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1168 ('FR', 'Friday'), ('SA', 'Saturday'), \
1169 ('SU', 'Sunday')], 'Weekday'),
1170 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1171 ('3', 'Third'), ('4', 'Fourth'), \
1172 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1173 'month_list': fields.selection(months.items(), 'Month'),
1174 'end_date': fields.date('Repeat Until'),
1175 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1176 'event_id', 'attendee_id', 'Attendees'),
1177 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1178 'active': fields.boolean('Active', help="If the active field is set to \
1179 true, it will allow you to hide the event alarm information without removing it."),
1180 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1181 'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
1183 def default_organizer(self, cr, uid, context=None):
1184 user_pool = self.pool.get('res.users')
1185 user = user_pool.browse(cr, uid, uid, context=context)
1188 res += " <%s>" %(user.user_email)
1192 'end_type' : 'forever',
1193 'state': 'tentative',
1200 'user_id': lambda self, cr, uid, ctx: uid,
1201 'organizer': default_organizer,
1203 'recurrent_id' : '',
1206 def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
1208 value = {'edit_all' : edit_all}
1209 self.write(cr, uid, ids, value, context=context)
1212 def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1214 Modifies the recurring event
1215 @param cr: the current row, from the database cursor,
1216 @param uid: the current user’s ID for security checks,
1217 @param event_ids: List of crm meeting’s IDs.
1218 @param context: A standard dictionary for contextual values
1221 for event_id in event_ids:
1222 event_id = base_calendar_id2real_id(event_id)
1225 defaults.update({'table': self._table})
1227 qry = "UPDATE %(table)s set name = '%(name)s', \
1228 date = '%(date)s', date_deadline = '%(date_deadline)s'"
1229 if defaults.get('alarm_id'):
1230 qry += ", alarm_id = %(alarm_id)s"
1231 if defaults.get('location'):
1232 qry += ", location = '%(location)s'"
1233 qry += "WHERE id = %s" % (event_id)
1234 cr.execute(qry, defaults)
1238 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
1239 """Gives virtual event ids for recurring events based on value of Recurrence Rule
1240 This method gives ids of dates that comes between start date and end date of calendar views
1241 @param self: The object pointer
1242 @param cr: the current row, from the database cursor,
1243 @param uid: the current user’s ID for security checks,
1244 @param base_start_date: Get Start Date
1245 @param base_until_date: Get End Date
1246 @param limit: The Number of Results to Return """
1250 virtual_id = context and context.get('virtual_id', False) or False
1252 if isinstance(select, (str, int, long)):
1258 if ids and virtual_id:
1259 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1260 m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1261 " m where m.id = ANY(%s)", (ids,) )
1263 for data in cr.dictfetchall():
1264 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1265 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1266 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1267 # To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1269 if not data['rrule']:
1270 start_date = event_date
1271 if start_date and (event_date < start_date):
1273 if until_date and (event_date > until_date):
1276 if not data['recurrent_id']:
1277 result.append(idval)
1279 ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1280 ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1281 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1282 if ls[1] == data['recurrent_id']:
1283 result.append(idval)
1284 recur_dict.append(ex_id)
1286 exdate = data['exdate'] and data['exdate'].split(',') or []
1287 rrule_str = data['rrule']
1289 rrule_until_date = False
1291 for rule in rrule_str.split(';'):
1292 name, value = rule.split('=')
1295 value = parser.parse(value)
1296 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1297 if until_date and until_date >= rrule_until_date:
1298 until_date = rrule_until_date
1300 value = until_date.strftime("%Y%m%d%H%M%S")
1301 new_rule = '%s=%s' % (name, value)
1302 new_rrule_str.append(new_rule)
1303 if not is_until and until_date:
1304 value = until_date.strftime("%Y%m%d%H%M%S")
1306 new_rule = '%s=%s' % (name, value)
1307 new_rrule_str.append(new_rule)
1308 new_rrule_str = ';'.join(new_rrule_str)
1309 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1311 for r_date in rdates:
1312 if start_date and r_date < start_date:
1314 if until_date and r_date > until_date:
1316 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1317 result.append(idval)
1320 ids = list(set(result)-set(recur_dict))
1321 if isinstance(select, (str, int, long)):
1322 return ids and ids[0] or False
1325 def compute_rule_string(self, cr, uid, datas, context=None, *args):
1327 Compute rule string according to value type RECUR of iCalendar from the values given.
1328 @param self: the object pointer
1329 @param cr: the current row, from the database cursor,
1330 @param uid: the current user’s ID for security checks,
1331 @param datas: dictionary of freq and interval value.
1332 @param context: A standard dictionary for contextual values
1333 @return: String value of the format RECUR of iCalendar
1336 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1340 freq=datas.get('rrule_type')
1344 interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1346 if freq == 'weekly':
1347 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1349 weekstring = ';BYDAY=' + ','.join(byday)
1351 elif freq == 'monthly':
1352 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1353 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1354 if datas.get('select1')=='day':
1355 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1356 elif datas.get('select1')=='date':
1357 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1360 if datas.get('end_date'):
1361 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1362 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1363 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1365 rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1366 + enddate + monthstring + yearstring
1371 def remove_virtual_id(self, ids):
1372 if isinstance(ids, (str, int)):
1373 return base_calendar_id2real_id(ids)
1375 if isinstance(ids, (list, tuple)):
1378 res.append(base_calendar_id2real_id(id))
1381 def search(self, cr, uid, args, offset=0, limit=0, order=None,
1382 context=None, count=False):
1383 print "limit begin", limit
1384 print "count", count
1385 args_without_date = []
1391 new_id = self.remove_virtual_id(arg[2])
1392 new_arg = (arg[0], arg[1], new_id)
1393 args_without_date.append(new_arg)
1394 elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1395 args_without_date.append(arg)
1397 if arg[1] in ('>', '>='):
1401 elif arg[1] in ('<', '<='):
1406 res = super(calendar_event, self).search(cr, uid, args_without_date, \
1407 0, 0, order, context, count=False)
1408 res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
1409 print "limit", limit
1410 print "offset", offset
1411 print "offset + limit", offset+limit
1413 print "len" , len(res)
1416 r = res[offset:offset+limit]
1420 print "longueur", len(r)
1424 def get_edit_all(self, cr, uid, id, vals=None):
1426 return true if we have to edit all meeting from the same recurrent
1427 or only on occurency
1429 meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1430 if(vals and 'edit_all' in vals): #we jsut check edit_all
1431 return vals['edit_all']
1432 else: #it's a recurrent event and edit_all is already check
1433 return meeting['recurrency'] and meeting['edit_all']
1438 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1441 if isinstance(ids, (str, int, long)):
1447 for event_id in select:
1448 real_event_id = base_calendar_id2real_id(event_id)
1451 if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1452 event_id = real_event_id
1454 #if edit one instance of a reccurrent id
1455 if len(str(event_id).split('-')) > 1:
1456 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1457 'rrule', 'duration', 'exdate'])
1458 if data.get('rrule'):
1461 'recurrent_uid': real_event_id,
1462 'recurrent_id': data.get('date'),
1463 'rrule_type': 'none',
1466 'recurrency' : False,
1469 new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1471 date_new = event_id.split('-')[1]
1472 date_new = time.strftime("%Y%m%dT%H%M%S", \
1473 time.strptime(date_new, "%Y%m%d%H%M%S"))
1474 exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
1475 res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1477 context.update({'active_id': new_id, 'active_ids': [new_id]})
1479 if not real_event_id in new_ids:
1480 new_ids.append(real_event_id)
1482 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1483 vals['vtimezone'] = vals['vtimezone'][40:]
1485 updated_vals = self.onchange_dates(cr, uid, new_ids,
1486 vals.get('date', False),
1487 vals.get('duration', False),
1488 vals.get('date_deadline', False),
1489 vals.get('allday', False),
1491 vals.update(updated_vals.get('value', {}))
1493 if not 'edit_all' in vals:
1494 vals['edit_all'] = False
1497 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1499 if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1500 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1501 # change alarm details
1502 alarm_obj = self.pool.get('res.alarm')
1503 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1504 return res or True and False
1506 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1507 if isinstance(ids, (str, int, long)):
1511 select = map(lambda x: base_calendar_id2real_id(x), select)
1512 res = super(calendar_event, self).browse(cr, uid, select, context, \
1513 list_class, fields_process)
1514 if isinstance(ids, (str, int, long)):
1515 return res and res[0] or False
1519 def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1523 if 'date' in groupby:
1524 raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1526 virtual_id = context.get('virtual_id', False)
1528 context.update({'virtual_id': False})
1529 res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1531 #remove the count, since the value is not consistent with the result of the search when expand the group
1532 for groupname in groupby:
1533 if re.get(groupname + "_count"):
1534 del re[groupname + "_count"]
1535 re.get('__context').update({'virtual_id' : virtual_id})
1538 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1539 # FIXME This whole id mangling has to go!
1545 if isinstance(ids, (str, int, long)):
1549 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1551 if fields and 'date' not in fields:
1552 fields.append('date')
1553 if fields and 'duration' not in fields:
1554 fields.append('duration')
1557 for base_calendar_id, real_id in select:
1558 #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1559 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1563 ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1564 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1566 res['date_deadline'] = ls[2]
1567 res['id'] = base_calendar_id
1570 if isinstance(ids, (str, int, long)):
1571 return result and result[0] or False
1575 def copy(self, cr, uid, id, default=None, context=None):
1578 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1579 alarm_obj = self.pool.get('res.alarm')
1580 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1584 def unlink(self, cr, uid, ids, context=None):
1586 for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1587 event_id = event_datas['id']
1589 if self.get_edit_all(cr, uid, event_id, vals=None):
1590 event_id = base_calendar_id2real_id(event_id)
1592 if isinstance(event_id, (int, long)):
1593 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1594 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1595 self.unlink_events(cr, uid, [event_id], context=context)
1597 str_event, date_new = event_id.split('-')
1598 event_id = int(str_event)
1599 if event_datas['rrule']:
1600 # Remove one of the recurrent event
1601 date_new = time.strftime("%Y%m%dT%H%M%S", \
1602 time.strptime(date_new, "%Y%m%d%H%M%S"))
1603 exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',') or '') + date_new
1604 res = self.write(cr, uid, [event_id], {'exdate': exdate})
1606 res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1607 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1608 self.unlink_events(cr, uid, [event_id], context=context)
1611 def create(self, cr, uid, vals, context=None):
1615 virtual_id = context.get('virtual_id', False)
1617 if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1618 vals['vtimezone'] = vals['vtimezone'][40:]
1620 updated_vals = self.onchange_dates(cr, uid, [],
1621 vals.get('date', False),
1622 vals.get('duration', False),
1623 vals.get('date_deadline', False),
1624 vals.get('allday', False),
1626 vals.update(updated_vals.get('value', {}))
1627 res = super(calendar_event, self).create(cr, uid, vals, context)
1628 alarm_obj = self.pool.get('res.alarm')
1629 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1635 def do_tentative(self, cr, uid, ids, context=None, *args):
1636 """ Makes event invitation as Tentative
1637 @param self: The object pointer
1638 @param cr: the current row, from the database cursor,
1639 @param uid: the current user’s ID for security checks,
1640 @param ids: List of Event IDs
1641 @param *args: Get Tupple value
1642 @param context: A standard dictionary for contextual values
1644 return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1646 def do_cancel(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': 'cancelled'}, context)
1657 def do_confirm(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': 'confirmed'}, context)
1670 class calendar_todo(osv.osv):
1671 """ Calendar Task """
1673 _name = "calendar.todo"
1674 _inherit = "calendar.event"
1675 _description = "Calendar Task"
1677 def _get_date(self, cr, uid, ids, name, arg, context=None):
1680 @param self: The object pointer
1681 @param cr: the current row, from the database cursor,
1682 @param uid: the current user’s ID for security checks,
1683 @param ids: List of calendar todo's IDs.
1684 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1685 @param context: A standard dictionary for contextual values
1689 for event in self.browse(cr, uid, ids, context=context):
1690 res[event.id] = event.date_start
1693 def _set_date(self, cr, uid, id, name, value, arg, context=None):
1696 @param self: The object pointer
1697 @param cr: the current row, from the database cursor,
1698 @param uid: the current user’s ID for security checks,
1699 @param id: calendar's ID.
1700 @param value: Get Value
1701 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1702 @param context: A standard dictionary for contextual values
1705 assert name == 'date'
1706 return self.write(cr, uid, id, { 'date_start': value }, context=context)
1709 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1710 string='Duration', store=True, type='datetime'),
1711 'duration': fields.integer('Duration'),
1719 class ir_attachment(osv.osv):
1720 _name = 'ir.attachment'
1721 _inherit = 'ir.attachment'
1723 def search_count(self, cr, user, args, context=None):
1725 @param self: The object pointer
1726 @param cr: the current row, from the database cursor,
1727 @param user: the current user’s ID for security checks,
1728 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1729 @param context: A standard dictionary for contextual values
1734 args1.append(map(lambda x:str(x).split('-')[0], arg))
1735 return super(ir_attachment, self).search_count(cr, user, args1, context)
1739 def create(self, cr, uid, vals, context=None):
1741 id = context.get('default_res_id', False)
1742 context.update({'default_res_id' : base_calendar_id2real_id(id)})
1743 return super(ir_attachment, self).create(cr, uid, vals, context=context)
1745 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1746 context=None, count=False):
1748 @param self: The object pointer
1749 @param cr: the current row, from the database cursor,
1750 @param uid: the current user’s ID for security checks,
1751 @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1752 @param offset: The Number of Results to pass,
1753 @param limit: The Number of Results to Return,
1754 @param context: A standard dictionary for contextual values
1758 for i, arg in enumerate(new_args):
1759 if arg[0] == 'res_id':
1760 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1762 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1763 limit=limit, order=order, context=context, count=False)
1766 class ir_values(osv.osv):
1767 _inherit = 'ir.values'
1769 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1770 isobject=False, meta=False, preserve_user=False, company=False):
1773 @param self: The object pointer
1774 @param cr: the current row, from the database cursor,
1775 @param uid: the current user’s ID for security checks,
1776 @param model: Get The Model
1781 if type(data) in (list, tuple):
1782 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1784 new_model.append(data)
1785 return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1786 value, replace, isobject, meta, preserve_user, company)
1788 def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1789 res_id_req=False, without_user=True, key2_req=True):
1792 @param self: The object pointer
1793 @param cr: the current row, from the database cursor,
1794 @param uid: the current user’s ID for security checks,
1795 @param model: Get The Model
1801 if type(data) in (list, tuple):
1802 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1804 new_model.append(data)
1805 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1806 meta, context, res_id_req, without_user, key2_req)
1810 class ir_model(osv.osv):
1812 _inherit = 'ir.model'
1814 def read(self, cr, uid, ids, fields=None, context=None,
1815 load='_classic_read'):
1817 Overrides orm read method.
1818 @param self: The object pointer
1819 @param cr: the current row, from the database cursor,
1820 @param uid: the current user’s ID for security checks,
1821 @param ids: List of IR Model’s IDs.
1822 @param context: A standard dictionary for contextual values
1824 new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1827 data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1828 context=context, load=load)
1831 val['id'] = base_calendar_id2real_id(val['id'])
1832 return isinstance(ids, (str, int, long)) and data[0] or data
1836 class virtual_report_spool(web_services.report_spool):
1838 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1841 @param self: The object pointer
1842 @param db: get the current database,
1843 @param uid: the current user’s ID for security checks,
1844 @param context: A standard dictionary for contextual values
1847 if object == 'printscreen.list':
1848 return super(virtual_report_spool, self).exp_report(db, uid, \
1849 object, ids, datas, context)
1852 new_ids.append(base_calendar_id2real_id(id))
1853 if datas.get('id', False):
1854 datas['id'] = base_calendar_id2real_id(datas['id'])
1855 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1857 virtual_report_spool()
1859 class res_users(osv.osv):
1860 _inherit = 'res.users'
1862 def _get_user_avail(self, cr, uid, ids, context=None):
1864 Get User Availability
1865 @param self: The object pointer
1866 @param cr: the current row, from the database cursor,
1867 @param uid: the current user’s ID for security checks,
1868 @param ids: List of res user’s IDs.
1869 @param context: A standard dictionary for contextual values
1872 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1874 attendee_obj = self.pool.get('calendar.attendee')
1875 attendee_ids = attendee_obj.search(cr, uid, [
1876 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1877 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1880 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1881 user_id = attendee_data['user_id']
1883 res.update({user_id:status})
1885 #TOCHECK: Delegated Event
1887 if user_id not in res:
1888 res[user_id] = 'free'
1892 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1894 Get User Availability Function
1895 @param self: The object pointer
1896 @param cr: the current row, from the database cursor,
1897 @param uid: the current user’s ID for security checks,
1898 @param ids: List of res user’s IDs.
1899 @param context: A standard dictionary for contextual values
1902 return self._get_user_avail(cr, uid, ids, context=context)
1905 'availability': fields.function(_get_user_avail_fun, type='selection', \
1906 selection=[('free', 'Free'), ('busy', 'Busy')], \
1907 string='Free/Busy', method=True),
1913 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: