6903e698e6a644113361f0f7927d1768fe948655
[odoo/odoo.git] / addons / calendar / calendar.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (c) 2011-2014 OpenERP S.A. <http://openerp.com>
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import pytz
23 import re
24 import time
25 import openerp
26 import openerp.service.report
27 import uuid
28 from werkzeug.exceptions import BadRequest
29 from datetime import datetime, timedelta
30 from dateutil import parser
31 from dateutil import rrule
32 from dateutil.relativedelta import relativedelta
33 from openerp import tools, SUPERUSER_ID
34 from openerp.osv import fields, osv
35 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
36 from openerp.tools.translate import _
37 from openerp.http import request
38 from operator import itemgetter
39
40 import logging
41 _logger = logging.getLogger(__name__)
42
43
44 def calendar_id2real_id(calendar_id=None, with_date=False):
45     """
46     Convert a "virtual/recurring event id" (type string) into a real event id (type int).
47     E.g. virtual/recurring event id is 4-20091201100000, so it will return 4.
48     @param calendar_id: id of calendar
49     @param with_date: if a value is passed to this param it will return dates based on value of withdate + calendar_id
50     @return: real event id
51     """
52     if calendar_id and isinstance(calendar_id, (str, unicode)):
53         res = calendar_id.split('-')
54         if len(res) >= 2:
55             real_id = res[0]
56             if with_date:
57                 real_date = time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(res[1], "%Y%m%d%H%M%S"))
58                 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
59                 end = start + timedelta(hours=with_date)
60                 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
61             return int(real_id)
62     return calendar_id and int(calendar_id) or calendar_id
63
64
65 def get_real_ids(ids):
66     if isinstance(ids, (str, int, long)):
67         return calendar_id2real_id(ids)
68
69     if isinstance(ids, (list, tuple)):
70         return [calendar_id2real_id(id) for id in ids]
71
72
73 class calendar_attendee(osv.Model):
74     """
75     Calendar Attendee Information
76     """
77     _name = 'calendar.attendee'
78     _rec_name = 'cn'
79     _description = 'Attendee information'
80
81     def _compute_data(self, cr, uid, ids, name, arg, context=None):
82         """
83         Compute data on function fields for attendee values.
84         @param ids: list of calendar attendee's IDs
85         @param name: name of field
86         @return: dictionary of form {id: {'field Name': value'}}
87         """
88         name = name[0]
89         result = {}
90         for attdata in self.browse(cr, uid, ids, context=context):
91             id = attdata.id
92             result[id] = {}
93             if name == 'cn':
94                 if attdata.partner_id:
95                     result[id][name] = attdata.partner_id.name or False
96                 else:
97                     result[id][name] = attdata.email or ''
98             if name == 'event_date':
99                 result[id][name] = attdata.event_id.date
100             if name == 'event_end_date':
101                 result[id][name] = attdata.event_id.date_deadline
102         return result
103
104     STATE_SELECTION = [
105         ('needsAction', 'Needs Action'),
106         ('tentative', 'Uncertain'),
107         ('declined', 'Declined'),
108         ('accepted', 'Accepted'),
109     ]
110
111     _columns = {
112         'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="Status of the attendee's participation"),
113         'cn': fields.function(_compute_data, string='Common name', type="char", multi='cn', store=True),
114         'partner_id': fields.many2one('res.partner', 'Contact', readonly="True"),
115         'email': fields.char('Email', help="Email of Invited Person"),
116         'event_date': fields.function(_compute_data, string='Event Date', type="datetime", multi='event_date'),
117         'event_end_date': fields.function(_compute_data, string='Event End Date', type="datetime", multi='event_end_date'),
118         'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
119         'access_token': fields.char('Invitation Token'),
120         'event_id': fields.many2one('calendar.event', 'Meeting linked'),
121     }
122     _defaults = {
123         'state': 'needsAction',
124     }
125
126     def copy(self, cr, uid, id, default=None, context=None):
127         raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
128
129     def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
130         """
131         Make entry on email and availability on change of partner_id field.
132         @param partner_id: changed value of partner id
133         """
134         if not partner_id:
135             return {'value': {'email': ''}}
136         partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
137         return {'value': {'email': partner.email}}
138
139     def get_ics_file(self, cr, uid, event_obj, context=None):
140         """
141         Returns iCalendar file for the event invitation.
142         @param event_obj: event object (browse record)
143         @return: .ics file content
144         """
145         res = None
146
147         def ics_datetime(idate, short=False):
148             if idate:
149                 return datetime.strptime(idate.split('.')[0], '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.timezone('UTC'))
150             return False
151
152         try:
153             # FIXME: why isn't this in CalDAV?
154             import vobject
155         except ImportError:
156             return res
157
158         cal = vobject.iCalendar()
159         event = cal.add('vevent')
160         if not event_obj.date_deadline or not event_obj.date:
161             raise osv.except_osv(_('Warning!'), _("First you have to specify the date of the invitation."))
162         event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
163         event.add('dtstart').value = ics_datetime(event_obj.date)
164         event.add('dtend').value = ics_datetime(event_obj.date_deadline)
165         event.add('summary').value = event_obj.name
166         if event_obj.description:
167             event.add('description').value = event_obj.description
168         if event_obj.location:
169             event.add('location').value = event_obj.location
170         if event_obj.rrule:
171             event.add('rrule').value = event_obj.rrule
172
173         if event_obj.alarm_ids:
174             for alarm in event_obj.alarm_ids:
175                 valarm = event.add('valarm')
176                 interval = alarm.interval
177                 duration = alarm.duration
178                 trigger = valarm.add('TRIGGER')
179                 trigger.params['related'] = ["START"]
180                 if interval == 'days':
181                     delta = timedelta(days=duration)
182                 elif interval == 'hours':
183                     delta = timedelta(hours=duration)
184                 elif interval == 'minutes':
185                     delta = timedelta(minutes=duration)
186                 trigger.value = delta
187                 valarm.add('DESCRIPTION').value = alarm.name or 'OpenERP'
188         for attendee in event_obj.attendee_ids:
189             attendee_add = event.add('attendee')
190             attendee_add.value = 'MAILTO:' + (attendee.email or '')
191         res = cal.serialize()
192         return res
193
194     def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='calendar_template_meeting_invitation', context=None):
195         """
196         Send mail for event invitation to event attendees.
197         @param email_from: email address for user sending the mail
198         """
199         res = False
200
201         if self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_mail', default=False):
202             return res
203
204         mail_ids = []
205         data_pool = self.pool['ir.model.data']
206         mailmess_pool = self.pool['mail.message']
207         mail_pool = self.pool['mail.mail']
208         template_pool = self.pool['email.template']
209         local_context = context.copy()
210         color = {
211             'needsAction': 'grey',
212             'accepted': 'green',
213             'tentative': '#FFFF00',
214             'declined': 'red'
215         }
216
217         if not isinstance(ids, (tuple, list)):
218             ids = [ids]
219
220         dummy, template_id = data_pool.get_object_reference(cr, uid, 'calendar', template_xmlid)
221         dummy, act_id = data_pool.get_object_reference(cr, uid, 'calendar', "view_calendar_event_calendar")
222         local_context.update({
223             'color': color,
224             'action_id': self.pool['ir.actions.act_window'].search(cr, uid, [('view_id', '=', act_id)], context=context)[0],
225             'dbname': cr.dbname,
226             'base_url': self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context)
227         })
228
229         for attendee in self.browse(cr, uid, ids, context=context):
230             if attendee.email and email_from and attendee.email != email_from:
231                 ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context)
232                 mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context)
233
234                 vals = {}
235                 if ics_file:
236                     vals['attachment_ids'] = [(0, 0, {'name': 'invitation.ics',
237                                                       'datas_fname': 'invitation.ics',
238                                                       'datas': str(ics_file).encode('base64')})]
239                 vals['model'] = None  # We don't want to have the mail in the tchatter while in queue!
240                 the_mailmess = mail_pool.browse(cr, uid, mail_id, context=context).mail_message_id
241                 mailmess_pool.write(cr, uid, [the_mailmess.id], vals, context=context)
242                 mail_ids.append(mail_id)
243
244         if mail_ids:
245             res = mail_pool.send(cr, uid, mail_ids, context=context)
246
247         return res
248
249     def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
250         """
251         Make entry on email and availability on change of user_id field.
252         @param ids: list of attendee's IDs
253         @param user_id: changed value of User id
254         @return: dictionary of values which put value in email and availability fields
255         """
256         if not user_id:
257             return {'value': {'email': ''}}
258
259         user = self.pool['res.users'].browse(cr, uid, user_id, *args)
260         return {'value': {'email': user.email, 'availability': user.availability}}
261
262     def do_tentative(self, cr, uid, ids, context=None, *args):
263         """
264         Makes event invitation as Tentative.
265         @param ids: list of attendee's IDs
266         """
267         return self.write(cr, uid, ids, {'state': 'tentative'}, context)
268
269     def do_accept(self, cr, uid, ids, context=None, *args):
270         """
271         Marks event invitation as Accepted.
272         @param ids: list of attendee's IDs
273         """
274         if context is None:
275             context = {}
276         meeting_obj = self.pool['calendar.event']
277         res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
278         for attendee in self.browse(cr, uid, ids, context=context):
279             meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context)
280
281         return res
282
283     def do_decline(self, cr, uid, ids, context=None, *args):
284         """
285         Marks event invitation as Declined.
286         @param ids: list of calendar attendee's IDs
287         """
288         if context is None:
289             context = {}
290         meeting_obj = self.pool['calendar.event']
291         res = self.write(cr, uid, ids, {'state': 'declined'}, context)
292         for attendee in self.browse(cr, uid, ids, context=context):
293             meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has declined invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context)
294         return res
295
296     def create(self, cr, uid, vals, context=None):
297         if context is None:
298             context = {}
299         if not vals.get("email") and vals.get("cn"):
300             cnval = vals.get("cn").split(':')
301             email = filter(lambda x: x.__contains__('@'), cnval)
302             vals['email'] = email and email[0] or ''
303             vals['cn'] = vals.get("cn")
304         res = super(calendar_attendee, self).create(cr, uid, vals, context=context)
305         return res
306
307
308 class res_partner(osv.Model):
309     _inherit = 'res.partner'
310     _columns = {
311         'calendar_last_notif_ack': fields.datetime('Last notification marked as read from base Calendar'),
312     }
313
314     def get_attendee_detail(self, cr, uid, ids, meeting_id, context=None):
315         datas = []
316         meeting = False
317         if meeting_id:
318             meeting = self.pool['calendar.event'].browse(cr, uid, get_real_ids(meeting_id), context=context)
319         for partner in self.browse(cr, uid, ids, context=context):
320             data = self.name_get(cr, uid, [partner.id], context)[0]
321             if meeting:
322                 for attendee in meeting.attendee_ids:
323                     if attendee.partner_id.id == partner.id:
324                         data = (data[0], data[1], attendee.state)
325             datas.append(data)
326         return datas
327
328     def calendar_last_notif_ack(self, cr, uid, context=None):
329         partner = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id
330         self.write(cr, uid, partner.id, {'calendar_last_notif_ack': datetime.now()}, context=context)
331         return
332
333
334 class calendar_alarm_manager(osv.AbstractModel):
335     _name = 'calendar.alarm_manager'
336
337     def get_next_potential_limit_alarm(self, cr, uid, seconds, notif=True, mail=True, partner_id=None, context=None):
338         res = {}
339         base_request = """
340                     SELECT
341                         cal.id,
342                         cal.date - interval '1' minute  * calcul_delta.max_delta AS first_alarm,
343                         CASE
344                             WHEN cal.recurrency THEN cal.end_date - interval '1' minute  * calcul_delta.min_delta
345                             ELSE cal.date_deadline - interval '1' minute  * calcul_delta.min_delta
346                         END as last_alarm,
347                         cal.date as first_event_date,
348                         CASE
349                             WHEN cal.recurrency THEN cal.end_date
350                             ELSE cal.date_deadline
351                         END as last_event_date,
352                         calcul_delta.min_delta,
353                         calcul_delta.max_delta,
354                         cal.rrule AS rule
355                     FROM
356                         calendar_event AS cal
357                         RIGHT JOIN
358                             (
359                                 SELECT
360                                     rel.calendar_event_id, max(alarm.duration_minutes) AS max_delta,min(alarm.duration_minutes) AS min_delta
361                                 FROM
362                                     calendar_alarm_calendar_event_rel AS rel
363                                         LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id
364                                 WHERE alarm.type in %s
365                                 GROUP BY rel.calendar_event_id
366                             ) AS calcul_delta ON calcul_delta.calendar_event_id = cal.id
367              """
368
369         filter_user = """
370                 RIGHT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
371                     AND part_rel.res_partner_id = %s
372         """
373
374         #Add filter on type
375         type_to_read = ()
376         if notif:
377             type_to_read += ('notification',)
378         if mail:
379             type_to_read += ('email',)
380
381         tuple_params = (type_to_read,)
382
383         #ADD FILTER ON PARTNER_ID
384         if partner_id:
385             base_request += filter_user
386             tuple_params += (partner_id, )
387
388         #Add filter on hours
389         tuple_params += (seconds, seconds,)
390
391         cr.execute("""SELECT *
392                         FROM ( %s ) AS ALL_EVENTS
393                        WHERE ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%%s' second )
394                          AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%%s' second )
395                    """ % base_request, tuple_params)
396
397         for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in cr.fetchall():
398             res[event_id] = {
399                 'event_id': event_id,
400                 'first_alarm': first_alarm,
401                 'last_alarm': last_alarm,
402                 'first_meeting': first_meeting,
403                 'last_meeting': last_meeting,
404                 'min_duration': min_duration,
405                 'max_duration': max_duration,
406                 'rrule': rule
407             }
408
409         return res
410
411     def do_check_alarm_for_one_date(self, cr, uid, one_date, event, event_maxdelta, in_the_next_X_seconds, after=False, notif=True, mail=True, context=None):
412         res = []
413         alarm_type = []
414
415         if notif:
416             alarm_type.append('notification')
417         if mail:
418             alarm_type.append('email')
419
420         if one_date - timedelta(minutes=event_maxdelta) < datetime.now() + timedelta(seconds=in_the_next_X_seconds):  # if an alarm is possible for this date
421             for alarm in event.alarm_ids:
422                 if alarm.type in alarm_type and \
423                     one_date - timedelta(minutes=alarm.duration_minutes) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \
424                         (not after or one_date - timedelta(minutes=alarm.duration_minutes) > datetime.strptime(after.split('.')[0], "%Y-%m-%d %H:%M:%S")):
425                         alert = {
426                             'alarm_id': alarm.id,
427                             'event_id': event.id,
428                             'notify_at': one_date - timedelta(minutes=alarm.duration_minutes),
429                         }
430                         res.append(alert)
431         return res
432
433     def get_next_mail(self, cr, uid, context=None):
434         try:
435             cron = self.pool['ir.model.data'].get_object(
436                 cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context)
437         except ValueError:
438             _logger.error("Cron for " + self._name + " can not be identified !")
439             return False
440
441         if cron.interval_type == "weeks":
442             cron_interval = cron.interval_number * 7 * 24 * 60 * 60
443         elif cron.interval_type == "days":
444             cron_interval = cron.interval_number * 24 * 60 * 60
445         elif cron.interval_type == "hours":
446             cron_interval = cron.interval_number * 60 * 60
447         elif cron.interval_type == "minutes":
448             cron_interval = cron.interval_number * 60
449         elif cron.interval_type == "seconds":
450             cron_interval = cron.interval_number
451         else:
452             cron_interval = False
453
454         if not cron_interval:
455             _logger.error("Cron delay can not be computed !")
456             return False
457
458         all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context)
459
460         for event in all_events:  # .values()
461             max_delta = all_events[event]['max_duration']
462             curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
463             if curEvent.recurrency:
464                 bFound = False
465                 LastFound = False
466                 for one_date in self.pool.get('calendar.event').get_recurrent_date_by_event(cr, uid, curEvent, context=context):
467                     in_date_format = one_date.replace(tzinfo=None)
468                     LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context)
469                     if LastFound:
470                         for alert in LastFound:
471                             self.do_mail_reminder(cr, uid, alert, context=context)
472
473                         if not bFound:  # if it's the first alarm for this recurrent event
474                             bFound = True
475                     if bFound and not LastFound:  # if the precedent event had an alarm but not this one, we can stop the search for this event
476                         break
477             else:
478                 in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S')
479                 LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context)
480                 if LastFound:
481                     for alert in LastFound:
482                         self.do_mail_reminder(cr, uid, alert, context=context)
483
484     def get_next_notif(self, cr, uid, context=None):
485         ajax_check_every_seconds = 300
486         partner = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id
487         all_notif = []
488
489         if not partner:
490             return []
491
492         all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner.id, mail=False, context=context)
493
494         for event in all_events:  # .values()
495             max_delta = all_events[event]['max_duration']
496             curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
497             if curEvent.recurrency:
498                 bFound = False
499                 LastFound = False
500                 for one_date in self.pool.get("calendar.event").get_recurrent_date_by_event(cr, uid, curEvent, context=context):
501                     in_date_format = one_date.replace(tzinfo=None)
502                     LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context)
503                     if LastFound:
504                         for alert in LastFound:
505                             all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
506                         if not bFound:  # if it's the first alarm for this recurrent event
507                             bFound = True
508                     if bFound and not LastFound:  # if the precedent event had alarm but not this one, we can stop the search fot this event
509                         break
510             else:
511                 in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S')
512                 LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, partner.calendar_last_notif_ack, mail=False, context=context)
513                 if LastFound:
514                     for alert in LastFound:
515                         all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
516         return all_notif
517
518     def do_mail_reminder(self, cr, uid, alert, context=None):
519         if context is None:
520             context = {}
521         res = False
522
523         event = self.pool['calendar.event'].browse(cr, uid, alert['event_id'], context=context)
524         alarm = self.pool['calendar.alarm'].browse(cr, uid, alert['alarm_id'], context=context)
525
526         if alarm.type == 'email':
527             res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], template_xmlid='calendar_template_meeting_reminder', context=context)
528
529         return res
530
531     def do_notif_reminder(self, cr, uid, alert, context=None):
532         alarm = self.pool['calendar.alarm'].browse(cr, uid, alert['alarm_id'], context=context)
533         event = self.pool['calendar.event'].browse(cr, uid, alert['event_id'], context=context)
534
535         if alarm.type == 'notification':
536             message = event.display_time
537
538             delta = alert['notify_at'] - datetime.now()
539             delta = delta.seconds + delta.days * 3600 * 24
540
541             return {
542                 'event_id': event.id,
543                 'title': event.name,
544                 'message': message,
545                 'timer': delta,
546                 'notify_at': alert['notify_at'].strftime("%Y-%m-%d %H:%M:%S"),
547             }
548
549
550 class calendar_alarm(osv.Model):
551     _name = 'calendar.alarm'
552     _description = 'Event alarm'
553
554     def _get_duration(self, cr, uid, ids, field_name, arg, context=None):
555         res = {}
556         for alarm in self.browse(cr, uid, ids, context=context):
557             if alarm.interval == "minutes":
558                 res[alarm.id] = alarm.duration
559             elif alarm.interval == "hours":
560                 res[alarm.id] = alarm.duration * 60
561             elif alarm.interval == "days":
562                 res[alarm.id] = alarm.duration * 60 * 24
563             else:
564                 res[alarm.id] = 0
565         return res
566
567     _columns = {
568         'name': fields.char('Name', required=True),  # fields function
569         'type': fields.selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True),
570         'duration': fields.integer('Amount', required=True),
571         'interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), ('days', 'Days')], 'Unit', required=True),
572         'duration_minutes': fields.function(_get_duration, type='integer', string='duration_minutes', store=True),
573     }
574
575     _defaults = {
576         'type': 'notification',
577         'duration': 1,
578         'interval': 'hours',
579     }
580
581     def _update_cron(self, cr, uid, context=None):
582         try:
583             cron = self.pool['ir.model.data'].get_object(
584                 cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context)
585         except ValueError:
586             return False
587         return cron.toggle(model=self._name, domain=[('type', '=', 'email')])
588
589     def create(self, cr, uid, values, context=None):
590         res = super(calendar_alarm, self).create(cr, uid, values, context=context)
591
592         self._update_cron(cr, uid, context=context)
593
594         return res
595
596     def write(self, cr, uid, ids, values, context=None):
597         res = super(calendar_alarm, self).write(cr, uid, ids, values, context=context)
598
599         self._update_cron(cr, uid, context=context)
600
601         return res
602
603     def unlink(self, cr, uid, ids, context=None):
604         res = super(calendar_alarm, self).unlink(cr, uid, ids, context=context)
605
606         self._update_cron(cr, uid, context=context)
607
608         return res
609
610
611 class ir_values(osv.Model):
612     _inherit = 'ir.values'
613
614     def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
615
616         new_model = []
617         for data in models:
618             if type(data) in (list, tuple):
619                 new_model.append((data[0], calendar_id2real_id(data[1])))
620             else:
621                 new_model.append(data)
622         return super(ir_values, self).set(cr, uid, key, key2, name, new_model,
623                                           value, replace, isobject, meta, preserve_user, company)
624
625     def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
626         if context is None:
627             context = {}
628         new_model = []
629         for data in models:
630             if type(data) in (list, tuple):
631                 new_model.append((data[0], calendar_id2real_id(data[1])))
632             else:
633                 new_model.append(data)
634         return super(ir_values, self).get(cr, uid, key, key2, new_model,
635                                           meta, context, res_id_req, without_user, key2_req)
636
637
638 class ir_model(osv.Model):
639
640     _inherit = 'ir.model'
641
642     def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
643         new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
644         if context is None:
645             context = {}
646         data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, context=context, load=load)
647         if data:
648             for val in data:
649                 val['id'] = calendar_id2real_id(val['id'])
650         return isinstance(ids, (str, int, long)) and data[0] or data
651
652
653 original_exp_report = openerp.service.report.exp_report
654
655
656 def exp_report(db, uid, object, ids, data=None, context=None):
657     """
658     Export Report
659     """
660     if object == 'printscreen.list':
661         original_exp_report(db, uid, object, ids, data, context)
662     new_ids = []
663     for id in ids:
664         new_ids.append(calendar_id2real_id(id))
665     if data.get('id', False):
666         data['id'] = calendar_id2real_id(data['id'])
667     return original_exp_report(db, uid, object, new_ids, data, context)
668
669
670 openerp.service.report.exp_report = exp_report
671
672
673 class calendar_event_type(osv.Model):
674     _name = 'calendar.event.type'
675     _description = 'Meeting Type'
676     _columns = {
677         'name': fields.char('Name', required=True, translate=True),
678     }
679
680
681 class calendar_event(osv.Model):
682     """ Model for Calendar Event """
683     _name = 'calendar.event'
684     _description = "Meeting"
685     _order = "id desc"
686     _inherit = ["mail.thread", "ir.needaction_mixin"]
687
688     def do_run_scheduler(self, cr, uid, id, context=None):
689         self.pool['calendar.alarm_manager'].get_next_mail(cr, uid, context=context)
690
691     def get_recurrent_date_by_event(self, cr, uid, event, context=None):
692         """Get recurrent dates based on Rule string and all event where recurrent_id is child
693         """
694
695         def todate(date):
696             val = parser.parse(''.join((re.compile('\d')).findall(date)))
697             ## Dates are localized to saved timezone if any, else current timezone.
698             if not val.tzinfo:
699                 val = pytz.UTC.localize(val)
700             return val.astimezone(timezone)
701
702         timezone = pytz.timezone(event.vtimezone or context.get('tz') or 'UTC')
703         startdate = pytz.UTC.localize(datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S"))  # Add "+hh:mm" timezone
704         if not startdate:
705             startdate = datetime.now()
706
707         ## Convert the start date to saved timezone (or context tz) as it'll
708         ## define the correct hour/day asked by the user to repeat for recurrence.
709         startdate = startdate.astimezone(timezone)  # transform "+hh:mm" timezone
710         rset1 = rrule.rrulestr(str(event.rrule), dtstart=startdate, forceset=True)
711         ids_depending = self.search(cr, uid, [('recurrent_id', '=', event.id), '|', ('active', '=', False), ('active', '=', True)], context=context)
712         all_events = self.browse(cr, uid, ids_depending, context=context)
713
714         for ev in all_events:
715             rset1._exdate.append(todate(ev.recurrent_id_date))
716
717         return [d.astimezone(pytz.UTC) for d in rset1]
718
719     def _get_recurrency_end_date(self, data, context=None):
720         if not data.get('recurrency'):
721             return False
722
723         end_type = data.get('end_type')
724         end_date = data.get('end_date')
725
726         if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'date_deadline']):
727             count = data['count'] + 1
728             delay, mult = {
729                 'daily': ('days', 1),
730                 'weekly': ('days', 7),
731                 'monthly': ('months', 1),
732                 'yearly': ('years', 1),
733             }[data['rrule_type']]
734
735             deadline = datetime.strptime(data['date_deadline'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
736             return deadline + relativedelta(**{delay: count * mult})
737         return end_date
738
739     def _find_my_attendee(self, cr, uid, meeting_ids, context=None):
740         """
741             Return the first attendee where the user connected has been invited from all the meeting_ids in parameters
742         """
743         user = self.pool['res.users'].browse(cr, uid, uid, context=context)
744         for meeting_id in meeting_ids:
745             for attendee in self.browse(cr, uid, meeting_id, context).attendee_ids:
746                 if user.partner_id.id == attendee.partner_id.id:
747                     return attendee
748         return False
749
750     def _get_display_time(self, cr, uid, meeting_id, context=None):
751         """
752             Return date and time (from to from) based on duration with timezone in string :
753             eg.
754             1) if user add duration for 2 hours, return : August-23-2013 at (04-30 To 06-30) (Europe/Brussels)
755             2) if event all day ,return : AllDay, July-31-2013
756         """
757         if context is None:
758             context = {}
759
760         tz = context.get('tz', False)
761         if not tz:  # tz can have a value False, so dont do it in the default value of get !
762             tz = pytz.timezone('UTC')
763
764         meeting = self.browse(cr, uid, meeting_id, context=context)
765         date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
766         date_deadline = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
767         event_date = date.strftime('%B-%d-%Y')
768         display_time = date.strftime('%H-%M')
769         if meeting.allday:
770             time = _("AllDay , %s") % (event_date)
771         elif meeting.duration < 24:
772             duration = date + timedelta(hours=meeting.duration)
773             time = ("%s at (%s To %s) (%s)") % (event_date, display_time, duration.strftime('%H-%M'), tz)
774         else:
775             time = ("%s at %s To\n %s at %s (%s)") % (event_date, display_time, date_deadline.strftime('%B-%d-%Y'), date_deadline.strftime('%H-%M'), tz)
776         return time
777
778     def _compute(self, cr, uid, ids, fields, arg, context=None):
779         res = {}
780         for meeting_id in ids:
781             res[meeting_id] = {}
782             attendee = self._find_my_attendee(cr, uid, [meeting_id], context)
783             for field in fields:
784                 if field == 'is_attendee':
785                     res[meeting_id][field] = True if attendee else False
786                 elif field == 'attendee_status':
787                     res[meeting_id][field] = attendee.state if attendee else 'needsAction'
788                 elif field == 'display_time':
789                     res[meeting_id][field] = self._get_display_time(cr, uid, meeting_id, context=context)
790         return res
791
792     def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
793         """
794         Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
795         @return: dictionary of rrule value.
796         """
797         result = {}
798         if not isinstance(ids, list):
799             ids = [ids]
800
801         for id in ids:
802             #read these fields as SUPERUSER because if the record is private a normal search could return False and raise an error
803             data = self.browse(cr, SUPERUSER_ID, id, context=context)
804
805             if data.interval and data.interval < 0:
806                 raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.'))
807             if data.count and data.count <= 0:
808                 raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.'))
809
810             data = self.read(cr, uid, id, ['id', 'byday', 'recurrency', 'month_list', 'end_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list'], context=context)
811             event = data['id']
812             if data['recurrency']:
813                 result[event] = self.compute_rule_string(data)
814             else:
815                 result[event] = ""
816         return result
817
818     # retro compatibility function
819     def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None):
820         return self._set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=context)
821
822     def _set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=None):
823         if not isinstance(ids, list):
824             ids = [ids]
825         data = self._get_empty_rrule_data()
826         if field_value:
827             data['recurrency'] = True
828             for event in self.browse(cr, uid, ids, context=context):
829                 rdate = event.date
830                 update_data = self._parse_rrule(field_value, dict(data), rdate)
831                 data.update(update_data)
832                 self.write(cr, uid, ids, data, context=context)
833         return True
834
835     def _tz_get(self, cr, uid, context=None):
836         return [(x.lower(), x) for x in pytz.all_timezones]
837
838     _track = {
839         'location': {
840             'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
841         },
842         'date': {
843             'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
844         },
845     }
846     _columns = {
847         'id': fields.integer('ID', readonly=True),
848         'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, track_visibility='onchange'),
849         'name': fields.char('Meeting Subject', required=True, states={'done': [('readonly', True)]}),
850         'is_attendee': fields.function(_compute, string='Attendee', type="boolean", multi='attendee'),
851         'attendee_status': fields.function(_compute, string='Attendee Status', type="selection", selection=calendar_attendee.STATE_SELECTION, multi='attendee'),
852         'display_time': fields.function(_compute, string='Event Time', type="char", multi='attendee'),
853         'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True, track_visibility='onchange'),
854         'date_deadline': fields.datetime('End Date', states={'done': [('readonly', True)]}, required=True,),
855         'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
856         'description': fields.text('Description', states={'done': [('readonly', True)]}),
857         'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
858         'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}),
859         'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}),
860         'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_set_rulestring, store=True, string='Recurrent Rule'),
861         'rrule_type': fields.selection([('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"),
862         'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
863         'recurrent_id': fields.integer('Recurrent ID'),
864         'recurrent_id_date': fields.datetime('Recurrent ID date'),
865         'vtimezone': fields.selection(_tz_get, string='Timezone'),
866         'end_type': fields.selection([('count', 'Number of repetitions'), ('end_date', 'End date')], 'Recurrence Termination'),
867         'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
868         'count': fields.integer('Repeat', help="Repeat x times"),
869         'mo': fields.boolean('Mon'),
870         'tu': fields.boolean('Tue'),
871         'we': fields.boolean('Wed'),
872         'th': fields.boolean('Thu'),
873         'fr': fields.boolean('Fri'),
874         'sa': fields.boolean('Sat'),
875         'su': fields.boolean('Sun'),
876         'month_by': fields.selection([('date', 'Date of month'), ('day', 'Day of month')], 'Option', oldname='select1'),
877         'day': fields.integer('Date of month'),
878         'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'), ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'), ('SU', 'Sunday')], 'Weekday'),
879         'byday': fields.selection([('1', 'First'), ('2', 'Second'), ('3', 'Third'), ('4', 'Fourth'), ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
880         'end_date': fields.date('Repeat Until'),
881         'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
882         'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
883         'color_partner_id': fields.related('user_id', 'partner_id', 'id', type="integer", string="colorize", store=False),  # Color of creator
884         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the event alarm information without removing it."),
885         'categ_ids': fields.many2many('calendar.event.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags'),
886         'attendee_ids': fields.one2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade'),
887         'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
888         'alarm_ids': fields.many2many('calendar.alarm', string='Reminders', ondelete="restrict"),
889
890     }
891
892     def _get_default_partners(self, cr, uid, ctx=None):
893         ret = [self.pool['res.users'].browse(cr, uid, uid, context=ctx).partner_id.id]
894         active_id = ctx.get('active_id')
895         if ctx.get('active_model') == 'res.partner' and active_id:
896             if active_id not in ret:
897                 ret.append(active_id)
898         return ret
899
900     _defaults = {
901         'end_type': 'count',
902         'count': 1,
903         'rrule_type': False,
904         'state': 'draft',
905         'class': 'public',
906         'show_as': 'busy',
907         'month_by': 'date',
908         'interval': 1,
909         'active': 1,
910         'user_id': lambda self, cr, uid, ctx: uid,
911         'partner_ids': _get_default_partners,
912     }
913
914     def _check_closing_date(self, cr, uid, ids, context=None):
915         for event in self.browse(cr, uid, ids, context=context):
916             if event.date_deadline < event.date:
917                 return False
918         return True
919
920     _constraints = [
921         (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']),
922     ]
923
924     def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
925
926         """Returns duration and/or end date based on values passed
927         @param ids: List of calendar event's IDs.
928         """
929         if context is None:
930             context = {}
931
932         value = {}
933         if not start_date:
934             return value
935
936         if not end_date and not duration:
937             duration = 1.00
938             value['duration'] = duration
939
940         if allday:  # For all day event
941             start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
942             user = self.pool['res.users'].browse(cr, uid, uid)
943             tz = pytz.timezone(user.tz) if user.tz else pytz.utc
944             start = pytz.utc.localize(start).astimezone(tz)     # convert start in user's timezone
945             start = start.astimezone(pytz.utc)                  # convert start back to utc
946
947             value['duration'] = 24.0
948             value['date'] = datetime.strftime(start, "%Y-%m-%d %H:%M:%S")
949         else:
950             start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
951
952         if end_date and not duration:
953             end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
954             diff = end - start
955             duration = float(diff.days) * 24 + (float(diff.seconds) / 3600)
956             value['duration'] = round(duration, 2)
957         elif not end_date:
958             end = start + timedelta(hours=duration)
959             value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
960         elif end_date and duration and not allday:
961             # we have both, keep them synchronized:
962             # set duration based on end_date (arbitrary decision: this avoid
963             # getting dates like 06:31:48 instead of 06:32:00)
964             end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
965             diff = end - start
966             duration = float(diff.days) * 24 + (float(diff.seconds) / 3600)
967             value['duration'] = round(duration, 2)
968
969         return {'value': value}
970
971     def new_invitation_token(self, cr, uid, record, partner_id):
972         return uuid.uuid4().hex
973
974     def create_attendees(self, cr, uid, ids, context):
975         user_obj = self.pool['res.users']
976         current_user = user_obj.browse(cr, uid, uid, context=context)
977         res = {}
978         for event in self.browse(cr, uid, ids, context):
979             attendees = {}
980             for att in event.attendee_ids:
981                 attendees[att.partner_id.id] = True
982             new_attendees = []
983             new_att_partner_ids = []
984             for partner in event.partner_ids:
985                 if partner.id in attendees:
986                     continue
987                 access_token = self.new_invitation_token(cr, uid, event, partner.id)
988                 values = {
989                     'partner_id': partner.id,
990                     'event_id': event.id,
991                     'access_token': access_token,
992                     'email': partner.email,
993                 }
994
995                 if partner.id == current_user.partner_id.id:
996                     values['state'] = 'accepted'
997
998                 att_id = self.pool['calendar.attendee'].create(cr, uid, values, context=context)
999                 new_attendees.append(att_id)
1000                 new_att_partner_ids.append(partner.id)
1001
1002                 if not current_user.email or current_user.email != partner.email:
1003                     mail_from = current_user.email or tools.config.get('email_from', False)
1004                     if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, att_id, email_from=mail_from, context=context):
1005                         self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee %s") % (partner.name,), subtype="calendar.subtype_invitation", context=context)
1006
1007             if new_attendees:
1008                 self.write(cr, uid, [event.id], {'attendee_ids': [(4, att) for att in new_attendees]}, context=context)
1009             if new_att_partner_ids:
1010                 self.message_subscribe(cr, uid, [event.id], new_att_partner_ids, context=context)
1011
1012             # We remove old attendees who are not in partner_ids now.
1013             all_partner_ids = [part.id for part in event.partner_ids]
1014             all_part_attendee_ids = [att.partner_id.id for att in event.attendee_ids]
1015             all_attendee_ids = [att.id for att in event.attendee_ids]
1016             partner_ids_to_remove = map(lambda x: x, set(all_part_attendee_ids + new_att_partner_ids) - set(all_partner_ids))
1017
1018             attendee_ids_to_remove = []
1019
1020             if partner_ids_to_remove:
1021                 attendee_ids_to_remove = self.pool["calendar.attendee"].search(cr, uid, [('partner_id.id', 'in', partner_ids_to_remove), ('event_id.id', '=', event.id)], context=context)
1022                 if attendee_ids_to_remove:
1023                     self.pool['calendar.attendee'].unlink(cr, uid, attendee_ids_to_remove, context)
1024
1025             res[event.id] = {
1026                 'new_attendee_ids': new_attendees,
1027                 'old_attendee_ids': all_attendee_ids,
1028                 'removed_attendee_ids': attendee_ids_to_remove
1029             }
1030         return res
1031
1032     def get_search_fields(self, browse_event, order_fields, r_date=None):
1033         sort_fields = {}
1034         for ord in order_fields:
1035             if ord == 'id' and r_date:
1036                 sort_fields[ord] = '%s-%s' % (browse_event[ord], r_date.strftime("%Y%m%d%H%M%S"))
1037             else:
1038                 sort_fields[ord] = browse_event[ord]
1039                 if type(browse_event[ord]) is openerp.osv.orm.browse_record:
1040                     name_get = browse_event[ord].name_get()
1041                     if len(name_get) and len(name_get[0]) >= 2:
1042                         sort_fields[ord] = name_get[0][1]
1043
1044         return sort_fields
1045
1046     def get_recurrent_ids(self, cr, uid, event_id, domain, order=None, context=None):
1047
1048         """Gives virtual event ids for recurring events
1049         This method gives ids of dates that comes between start date and end date of calendar views
1050
1051         @param order: The fields (comma separated, format "FIELD {DESC|ASC}") on which the events should be sorted
1052         """
1053
1054         if not context:
1055             context = {}
1056
1057         if isinstance(event_id, (str, int, long)):
1058             ids_to_browse = [event_id]  # keep select for return
1059         else:
1060             ids_to_browse = event_id
1061
1062         if order:
1063             order_fields = [field.split()[0] for field in order.split(',')]
1064         else:
1065             # fallback on self._order defined on the model
1066             order_fields = [field.split()[0] for field in self._order.split(',')]
1067
1068         if 'id' not in order_fields:
1069             order_fields.append('id')
1070
1071         result_data = []
1072         result = []
1073         for ev in self.browse(cr, uid, ids_to_browse, context=context):
1074             if not ev.recurrency or not ev.rrule:
1075                 result.append(ev.id)
1076                 result_data.append(self.get_search_fields(ev, order_fields))
1077                 continue
1078
1079             rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context)
1080
1081             for r_date in rdates:
1082                 # fix domain evaluation
1083                 # step 1: check date and replace expression by True or False, replace other expressions by True
1084                 # step 2: evaluation of & and |
1085                 # check if there are one False
1086                 pile = []
1087                 ok = True
1088                 for arg in domain:
1089                     if str(arg[0]) in (str('date'), str('date_deadline'), str('end_date')):
1090                         if (arg[1] == '='):
1091                             ok = r_date.strftime('%Y-%m-%d') == arg[2]
1092                         if (arg[1] == '>'):
1093                             ok = r_date.strftime('%Y-%m-%d') > arg[2]
1094                         if (arg[1] == '<'):
1095                             ok = r_date.strftime('%Y-%m-%d') < arg[2]
1096                         if (arg[1] == '>='):
1097                             ok = r_date.strftime('%Y-%m-%d') >= arg[2]
1098                         if (arg[1] == '<='):
1099                             ok = r_date.strftime('%Y-%m-%d') <= arg[2]
1100                         pile.append(ok)
1101                     elif str(arg) == str('&') or str(arg) == str('|'):
1102                         pile.append(arg)
1103                     else:
1104                         pile.append(True)
1105                 pile.reverse()
1106                 new_pile = []
1107                 for item in pile:
1108                     if not isinstance(item, basestring):
1109                         res = item
1110                     elif str(item) == str('&'):
1111                         first = new_pile.pop()
1112                         second = new_pile.pop()
1113                         res = first and second
1114                     elif str(item) == str('|'):
1115                         first = new_pile.pop()
1116                         second = new_pile.pop()
1117                         res = first or second
1118                     new_pile.append(res)
1119
1120                 if [True for item in new_pile if not item]:
1121                     continue
1122                 result_data.append(self.get_search_fields(ev, order_fields, r_date=r_date))
1123
1124         if order_fields:
1125             def comparer(left, right):
1126                 for fn, mult in comparers:
1127                     result = cmp(fn(left), fn(right))
1128                     if result:
1129                         return mult * result
1130                 return 0
1131
1132             sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
1133             comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
1134             ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
1135
1136         if isinstance(event_id, (str, int, long)):
1137             return ids and ids[0] or False
1138         else:
1139             return ids
1140
1141     def compute_rule_string(self, data):
1142         """
1143         Compute rule string according to value type RECUR of iCalendar from the values given.
1144         @param self: the object pointer
1145         @param data: dictionary of freq and interval value
1146         @return: string containing recurring rule (empty if no rule)
1147         """
1148         def get_week_string(freq, data):
1149             weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1150             if freq == 'weekly':
1151                 byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data))
1152                 # byday = map(lambda x: x.upper(),[data[day] for day in weekdays if data[day]])
1153
1154                 if byday:
1155                     return ';BYDAY=' + ','.join(byday)
1156             return ''
1157
1158         def get_month_string(freq, data):
1159             if freq == 'monthly':
1160                 if data.get('month_by') == 'date' and (data.get('day') < 1 or data.get('day') > 31):
1161                     raise osv.except_osv(_('Error!'), ("Please select a proper day of the month."))
1162
1163                 if data.get('month_by') == 'day':  # Eg : Second Monday of the month
1164                     return ';BYDAY=' + data.get('byday') + data.get('week_list')
1165                 elif data.get('month_by') == 'date':  # Eg : 16th of the month
1166                     return ';BYMONTHDAY=' + str(data.get('day'))
1167             return ''
1168
1169         def get_end_date(data):
1170             if data.get('end_date'):
1171                 data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z'
1172
1173             return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\
1174                 ((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '')
1175
1176         freq = data.get('rrule_type', False)  # day/week/month/year
1177         res = ''
1178         if freq:
1179             interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or ''
1180             res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data)
1181
1182         return res
1183
1184     def _get_empty_rrule_data(self):
1185         return {
1186             'byday': False,
1187             'recurrency': False,
1188             'end_date': False,
1189             'rrule_type': False,
1190             'month_by': False,
1191             'interval': 0,
1192             'count': False,
1193             'end_type': False,
1194             'mo': False,
1195             'tu': False,
1196             'we': False,
1197             'th': False,
1198             'fr': False,
1199             'sa': False,
1200             'su': False,
1201             'day': False,
1202             'week_list': False
1203         }
1204
1205     def _parse_rrule(self, rule, data, date_start):
1206         day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1207         rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
1208         r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S"))
1209
1210         if r._freq > 0 and r._freq < 4:
1211             data['rrule_type'] = rrule_type[r._freq]
1212
1213         data['count'] = r._count
1214         data['interval'] = r._interval
1215         data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S")
1216         #repeat weekly
1217         if r._byweekday:
1218             for i in xrange(0, 7):
1219                 if i in r._byweekday:
1220                     data[day_list[i]] = True
1221             data['rrule_type'] = 'weekly'
1222         #repeat monthly by nweekday ((weekday, weeknumber), )
1223         if r._bynweekday:
1224             data['week_list'] = day_list[r._bynweekday[0][0]].upper()
1225             data['byday'] = str(r._bynweekday[0][1])
1226             data['month_by'] = 'day'
1227             data['rrule_type'] = 'monthly'
1228
1229         if r._bymonthday:
1230             data['day'] = r._bymonthday[0]
1231             data['month_by'] = 'date'
1232             data['rrule_type'] = 'monthly'
1233
1234         #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
1235         if r._bymonth:
1236             data['interval'] = data['interval'] * 12
1237
1238         #FIXEME handle forever case
1239         #end of recurrence
1240         #in case of repeat for ever that we do not support right now
1241         if not (data.get('count') or data.get('end_date')):
1242             data['count'] = 100
1243         if data.get('count'):
1244             data['end_type'] = 'count'
1245         else:
1246             data['end_type'] = 'end_date'
1247         return data
1248
1249     def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
1250         res = {}
1251         for virtual_id in ids:
1252             real_id = calendar_id2real_id(virtual_id)
1253             result = super(calendar_event, self).message_get_subscription_data(cr, uid, [real_id], user_pid=None, context=context)
1254             res[virtual_id] = result[real_id]
1255         return res
1256
1257     def onchange_partner_ids(self, cr, uid, ids, value, context=None):
1258         """ The basic purpose of this method is to check that destination partners
1259             effectively have email addresses. Otherwise a warning is thrown.
1260             :param value: value format: [[6, 0, [3, 4]]]
1261         """
1262         res = {'value': {}}
1263
1264         if not value or not value[0] or not value[0][0] == 6:
1265             return
1266
1267         res.update(self.check_partners_email(cr, uid, value[0][2], context=context))
1268         return res
1269
1270     def onchange_rec_day(self, cr, uid, id, date, mo, tu, we, th, fr, sa, su):
1271         """ set the start date according to the first occurence of rrule"""
1272         rrule_obj = self._get_empty_rrule_data()
1273         rrule_obj.update({
1274             'byday': True,
1275             'rrule_type': 'weekly',
1276             'mo': mo,
1277             'tu': tu,
1278             'we': we,
1279             'th': th,
1280             'fr': fr,
1281             'sa': sa,
1282             'su': su,
1283             'interval': 1
1284         })
1285         str_rrule = self.compute_rule_string(rrule_obj)
1286         first_occurence = list(rrule.rrulestr(str_rrule + ";COUNT=1", dtstart=datetime.strptime(date, "%Y-%m-%d %H:%M:%S"), forceset=True))[0]
1287         return {'value': {'date': first_occurence.strftime("%Y-%m-%d") + ' 00:00:00'}}
1288
1289     def check_partners_email(self, cr, uid, partner_ids, context=None):
1290         """ Verify that selected partner_ids have an email_address defined.
1291             Otherwise throw a warning. """
1292         partner_wo_email_lst = []
1293         for partner in self.pool['res.partner'].browse(cr, uid, partner_ids, context=context):
1294             if not partner.email:
1295                 partner_wo_email_lst.append(partner)
1296         if not partner_wo_email_lst:
1297             return {}
1298         warning_msg = _('The following contacts have no email address :')
1299         for partner in partner_wo_email_lst:
1300             warning_msg += '\n- %s' % (partner.name)
1301         return {'warning': {
1302                 'title': _('Email addresses not found'),
1303                 'message': warning_msg,
1304                 }}
1305
1306     # ----------------------------------------
1307     # OpenChatter
1308     # ----------------------------------------
1309
1310     # shows events of the day for this user
1311
1312     def _needaction_domain_get(self, cr, uid, context=None):
1313         return [('end_date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('user_id', '=', uid)]
1314
1315     def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
1316         if isinstance(thread_id, str):
1317             thread_id = get_real_ids(thread_id)
1318         if context.get('default_date'):
1319             del context['default_date']
1320         return super(calendar_event, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
1321
1322     def do_sendmail(self, cr, uid, ids, context=None):
1323         for event in self.browse(cr, uid, ids, context):
1324             current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
1325
1326             if current_user.email:
1327                 if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], email_from=current_user.email, context=context):
1328                     self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), subtype="calendar.subtype_invitation", context=context)
1329         return
1330
1331     def get_attendee(self, cr, uid, meeting_id, context=None):
1332         # Used for view in controller
1333         invitation = {'meeting': {}, 'attendee': []}
1334
1335         meeting = self.browse(cr, uid, int(meeting_id), context)
1336         invitation['meeting'] = {
1337             'event': meeting.name,
1338             'where': meeting.location,
1339             'when': meeting.display_time
1340         }
1341
1342         for attendee in meeting.attendee_ids:
1343             invitation['attendee'].append({'name': attendee.cn, 'status': attendee.state})
1344         return invitation
1345
1346     def get_interval(self, cr, uid, ids, date, interval, tz=None, context=None):
1347         #Function used only in calendar_event_data.xml for email template
1348         date = datetime.strptime(date.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT)
1349
1350         if tz:
1351             timezone = pytz.timezone(tz or 'UTC')
1352             date = date.replace(tzinfo=pytz.timezone('UTC')).astimezone(timezone)
1353
1354         if interval == 'day':
1355             res = str(date.day)
1356         elif interval == 'month':
1357             res = date.strftime('%B') + " " + str(date.year)
1358         elif interval == 'dayname':
1359             res = date.strftime('%A')
1360         elif interval == 'time':
1361             res = date.strftime('%I:%M %p')
1362         return res
1363
1364     def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1365         if context is None:
1366             context = {}
1367
1368         if context.get('mymeetings', False):
1369             partner_id = self.pool['res.users'].browse(cr, uid, uid, context).partner_id.id
1370             args += ['|', ('partner_ids', 'in', [partner_id]), ('user_id', '=', uid)]
1371
1372         new_args = []
1373         for arg in args:
1374             new_arg = arg
1375
1376             if arg[0] in ('date', unicode('date')) and arg[1] == ">=":
1377                 if context.get('virtual_id', True):
1378                     new_args += ['|', '&', ('recurrency', '=', 1), ('end_date', arg[1], arg[2])]
1379             elif arg[0] == "id":
1380                 new_id = get_real_ids(arg[2])
1381                 new_arg = (arg[0], arg[1], new_id)
1382             new_args.append(new_arg)
1383
1384         if not context.get('virtual_id', True):
1385             return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count)
1386
1387         # offset, limit, order and count must be treated separately as we may need to deal with virtual ids
1388         res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
1389         res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context)
1390         if count:
1391             return len(res)
1392         elif limit:
1393             return res[offset: offset + limit]
1394         return res
1395
1396     def copy(self, cr, uid, id, default=None, context=None):
1397         if context is None:
1398             context = {}
1399
1400         default = default or {}
1401         default['attendee_ids'] = False
1402
1403         res = super(calendar_event, self).copy(cr, uid, calendar_id2real_id(id), default, context)
1404         return res
1405
1406     def _detach_one_event(self, cr, uid, id, values=dict(), context=None):
1407         real_event_id = calendar_id2real_id(id)
1408         data = self.read(cr, uid, id, ['date', 'date_deadline', 'rrule', 'duration'])
1409
1410         if data.get('rrule'):
1411             data.update(
1412                 values,
1413                 recurrent_id=real_event_id,
1414                 recurrent_id_date=data.get('date'),
1415                 rrule_type=False,
1416                 rrule='',
1417                 recurrency=False,
1418                 end_date=datetime.strptime(values.get('date', False) or data.get('date'), "%Y-%m-%d %H:%M:%S") + timedelta(hours=values.get('duration', False) or data.get('duration'))
1419             )
1420
1421             #do not copy the id
1422             if data.get('id'):
1423                 del(data['id'])
1424             new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1425             return new_id
1426
1427     def open_after_detach_event(self, cr, uid, ids, context=None):
1428         if context is None:
1429             context = {}
1430
1431         new_id = self._detach_one_event(cr, uid, ids[0], context=context)
1432         return {
1433             'type': 'ir.actions.act_window',
1434             'res_model': 'calendar.event',
1435             'view_mode': 'form',
1436             'res_id': new_id,
1437             'target': 'current',
1438             'flags': {'form': {'action_buttons': True, 'options': {'mode': 'edit'}}}
1439         }
1440
1441     def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
1442         for arg in args:
1443             if arg[0] == 'id':
1444                 for n, calendar_id in enumerate(arg[2]):
1445                     if isinstance(calendar_id, str):
1446                         arg[2][n] = calendar_id.split('-')[0]
1447         return super(calendar_event, self)._name_search(cr, user, name=name, args=args, operator=operator, context=context, limit=limit, name_get_uid=name_get_uid)
1448
1449     def write(self, cr, uid, ids, values, context=None):
1450         def _only_changes_to_apply_on_real_ids(field_names):
1451             ''' return True if changes are only to be made on the real ids'''
1452             for field in field_names:
1453                 if field in ['date', 'active']:
1454                     return True
1455             return False
1456
1457         context = context or {}
1458
1459         if isinstance(ids, (str, int, long)):
1460             if len(str(ids).split('-')) == 1:
1461                 ids = [int(ids)]
1462             else:
1463                 ids = [ids]
1464
1465         res = False
1466         new_id = False
1467
1468          # Special write of complex IDS
1469         for event_id in ids:
1470             if len(str(event_id).split('-')) == 1:
1471                 continue
1472
1473             ids.remove(event_id)
1474             real_event_id = calendar_id2real_id(event_id)
1475
1476             # if we are setting the recurrency flag to False or if we are only changing fields that
1477             # should be only updated on the real ID and not on the virtual (like message_follower_ids):
1478             # then set real ids to be updated.
1479             if not values.get('recurrency', True) or not _only_changes_to_apply_on_real_ids(values.keys()):
1480                 ids.append(real_event_id)
1481                 continue
1482             else:
1483                 data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
1484                 if data.get('rrule'):
1485                     new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
1486
1487         res = super(calendar_event, self).write(cr, uid, ids, values, context=context)
1488
1489         # set end_date for calendar searching
1490         if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \
1491                 (values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')):
1492             for data in self.read(cr, uid, ids, ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
1493                 end_date = self._get_recurrency_end_date(data, context=context)
1494                 super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context)
1495
1496         attendees_create = False
1497         if values.get('partner_ids', False):
1498             attendees_create = self.create_attendees(cr, uid, ids, context)
1499
1500         if values.get('date', False) and values.get('active', True):
1501             the_id = new_id or (ids and int(ids[0]))
1502
1503             if attendees_create:
1504                 attendees_create = attendees_create[the_id]
1505                 mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
1506             else:
1507                 mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
1508
1509             if mail_to_ids:
1510                 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
1511                 if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
1512                     self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
1513
1514         return res or True and False
1515
1516     def create(self, cr, uid, vals, context=None):
1517         if context is None:
1518             context = {}
1519
1520         if not 'user_id' in vals:  # Else bug with quick_create when we are filter on an other user
1521             vals['user_id'] = uid
1522
1523         res = super(calendar_event, self).create(cr, uid, vals, context=context)
1524
1525         data = self.read(cr, uid, [res], ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context)[0]
1526         end_date = self._get_recurrency_end_date(data, context=context)
1527         self.write(cr, uid, [res], {'end_date': end_date}, context=context)
1528
1529         self.create_attendees(cr, uid, [res], context=context)
1530         return res
1531
1532     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1533         if not context:
1534             context = {}
1535
1536         if 'date' in groupby:
1537             raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.'))
1538         virtual_id = context.get('virtual_id', True)
1539         context.update({'virtual_id': False})
1540         res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1541         for result in res:
1542             #remove the count, since the value is not consistent with the result of the search when expand the group
1543             for groupname in groupby:
1544                 if result.get(groupname + "_count"):
1545                     del result[groupname + "_count"]
1546             result.get('__context', {}).update({'virtual_id': virtual_id})
1547         return res
1548
1549     def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1550         if context is None:
1551             context = {}
1552         fields2 = fields and fields[:] or None
1553         EXTRAFIELDS = ('class', 'user_id', 'duration', 'date', 'rrule', 'vtimezone')
1554         for f in EXTRAFIELDS:
1555             if fields and (f not in fields):
1556                 fields2.append(f)
1557
1558         if isinstance(ids, (str, int, long)):
1559             select = [ids]
1560         else:
1561             select = ids
1562
1563         select = map(lambda x: (x, calendar_id2real_id(x)), select)
1564         result = []
1565
1566         real_data = super(calendar_event, self).read(cr, uid, [real_id for calendar_id, real_id in select], fields=fields2, context=context, load=load)
1567         real_data = dict(zip([x['id'] for x in real_data], real_data))
1568
1569         for calendar_id, real_id in select:
1570             res = real_data[real_id].copy()
1571             ls = calendar_id2real_id(calendar_id, with_date=res and res.get('duration', 0) or 0)
1572             if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1573                 res['date'] = ls[1]
1574                 res['date_deadline'] = ls[2]
1575             res['id'] = calendar_id
1576             result.append(res)
1577
1578         for r in result:
1579             if r['user_id']:
1580                 user_id = type(r['user_id']) in (tuple, list) and r['user_id'][0] or r['user_id']
1581                 if user_id == uid:
1582                     continue
1583             if r['class'] == 'private':
1584                 for f in r.keys():
1585                     if f not in ('id', 'date', 'date_deadline', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
1586                         if isinstance(r[f], list):
1587                             r[f] = []
1588                         else:
1589                             r[f] = False
1590                     if f == 'name':
1591                         r[f] = _('Busy')
1592
1593         for r in result:
1594             for k in EXTRAFIELDS:
1595                 if (k in r) and (fields and (k not in fields)):
1596                     del r[k]
1597
1598         if isinstance(ids, (str, int, long)):
1599             return result and result[0] or False
1600         return result
1601
1602     def unlink(self, cr, uid, ids, unlink_level=0, context=None):
1603         if not isinstance(ids, list):
1604             ids = [ids]
1605         res = False
1606
1607         ids_to_exclure = []
1608         ids_to_unlink = []
1609
1610         # One time moved to google_Calendar, we can specify, if not in google, and not rec or get_inst = 0, we delete it
1611         for event_id in ids:
1612             if unlink_level == 1 and len(str(event_id).split('-')) == 1:  # if  ID REAL
1613                 if self.browse(cr, uid, event_id).recurrent_id:
1614                     ids_to_exclure.append(event_id)
1615                 else:
1616                     ids_to_unlink.append(event_id)
1617             else:
1618                 ids_to_exclure.append(event_id)
1619
1620         if ids_to_unlink:
1621             res = super(calendar_event, self).unlink(cr, uid, ids_to_unlink, context=context)
1622
1623         if ids_to_exclure:
1624             for id_to_exclure in ids_to_exclure:
1625                 res = self.write(cr, uid, id_to_exclure, {'active': False}, context=context)
1626
1627         return res
1628
1629
1630 class mail_message(osv.Model):
1631     _inherit = "mail.message"
1632
1633     def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1634         '''
1635         convert the search on real ids in the case it was asked on virtual ids, then call super()
1636         '''
1637         for index in range(len(args)):
1638             if args[index][0] == "res_id" and isinstance(args[index][2], str):
1639                 args[index][2] = get_real_ids(args[index][2])
1640         return super(mail_message, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
1641
1642     def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None):
1643         if context is None:
1644             context = {}
1645         if doc_model == 'calendar.event':
1646             order = context.get('order', self._order)
1647             for virtual_id in self.pool[doc_model].get_recurrent_ids(cr, uid, doc_dict.keys(), [], order=order, context=context):
1648                 doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)])
1649         return super(mail_message, self)._find_allowed_model_wise(cr, uid, doc_model, doc_dict, context=context)
1650
1651
1652 class ir_attachment(osv.Model):
1653     _inherit = "ir.attachment"
1654
1655     def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1656         '''
1657         convert the search on real ids in the case it was asked on virtual ids, then call super()
1658         '''
1659         for index in range(len(args)):
1660             if args[index][0] == "res_id" and isinstance(args[index][2], str):
1661                 args[index][2] = get_real_ids(args[index][2])
1662         return super(ir_attachment, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
1663
1664     def write(self, cr, uid, ids, vals, context=None):
1665         '''
1666         when posting an attachment (new or not), convert the virtual ids in real ids.
1667         '''
1668         if isinstance(vals.get('res_id'), str):
1669             vals['res_id'] = get_real_ids(vals.get('res_id'))
1670         return super(ir_attachment, self).write(cr, uid, ids, vals, context=context)
1671
1672
1673 class ir_http(osv.AbstractModel):
1674     _inherit = 'ir.http'
1675
1676     def _auth_method_calendar(self):
1677         token = request.params['token']
1678         db = request.params['db']
1679
1680         registry = openerp.modules.registry.RegistryManager.get(db)
1681         attendee_pool = registry.get('calendar.attendee')
1682         error_message = False
1683         with registry.cursor() as cr:
1684             attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token', '=', token)])
1685             if not attendee_id:
1686                 error_message = """Invalid Invitation Token."""
1687             elif request.session.uid and request.session.login != 'anonymous':
1688                  # if valid session but user is not match
1689                 attendee = attendee_pool.browse(cr, openerp.SUPERUSER_ID, attendee_id[0])
1690                 user = registry.get('res.users').browse(cr, openerp.SUPERUSER_ID, request.session.uid)
1691                 if attendee.partner_id.id != user.partner_id.id:
1692                     error_message = """Invitation cannot be forwarded via email. This event/meeting belongs to %s and you are logged in as %s. Please ask organizer to add you.""" % (attendee.email, user.email)
1693
1694         if error_message:
1695             raise BadRequest(error_message)
1696         return True
1697
1698
1699 class invite_wizard(osv.osv_memory):
1700     _inherit = 'mail.wizard.invite'
1701
1702     def default_get(self, cr, uid, fields, context=None):
1703         '''
1704         in case someone clicked on 'invite others' wizard in the followers widget, transform virtual ids in real ids
1705         '''
1706         result = super(invite_wizard, self).default_get(cr, uid, fields, context=context)
1707         if 'res_id' in result:
1708             result['res_id'] = get_real_ids(result['res_id'])
1709         return result