hunk.code_window:     ]
       state = fields.Selection(STATE_SELECTION, string='Status', readonly=True, default='needsAction',
           help="Status of the attendee's participation")
       cn = fields.Char('Common name', compute='_compute_common_name', store=True)
       partner_id = fields.Many2one('res.partner', 'Contact', readonly="True")
       email = fields.Char('Email', help="Email of Invited Person")
       availability = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True")
  
hunk.labels: keep keep keep keep replace keep keep keep
hunk.after_edit:     common_name = fields.Char('Common name', compute='_compute_common_name', store=True)
  
hunk.file_path: addons/calendar/models/calendar.py
hunk.type: replace
file: # -*- coding: utf-8 -*-
  # Part of Odoo. See LICENSE file for full copyright and licensing details.
  
  import babel.dates
  import collections
  from datetime import datetime, timedelta
  from dateutil import parser
  from dateutil import rrule
  from dateutil.relativedelta import relativedelta
  import logging
  from operator import itemgetter
  import pytz
  import re
  import time
  import uuid
  
  from odoo import api, fields, models
  from odoo import tools
  from odoo.tools.translate import _
  from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
  from odoo.exceptions import UserError, ValidationError
  
  
  _logger = logging.getLogger(__name__)
  
  VIRTUALID_DATETIME_FORMAT = "%Y%m%d%H%M%S"
  
  
  def calendar_id2real_id(calendar_id=None, with_date=False):
      """ Convert a "virtual/recurring event id" (type string) into a real event id (type int).
          E.g. virtual/recurring event id is 4-20091201100000, so it will return 4.
          :param calendar_id: id of calendar
          :param with_date: if a value is passed to this param it will return dates based on value of withdate + calendar_id
          :return: real event id
      """
      if calendar_id and isinstance(calendar_id, (basestring)):
          res = filter(None, calendar_id.split('-'))
          if len(res) == 2:
              real_id = res[0]
              if with_date:
                  real_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT, time.strptime(res[1], VIRTUALID_DATETIME_FORMAT))
                  start = datetime.strptime(real_date, DEFAULT_SERVER_DATETIME_FORMAT)
                  end = start + timedelta(hours=with_date)
                  return (int(real_id), real_date, end.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
              return int(real_id)
      return calendar_id and int(calendar_id) or calendar_id
  
  
  def get_real_ids(ids):
      if isinstance(ids, (basestring, int, long)):
          return calendar_id2real_id(ids)
  
      if isinstance(ids, (list, tuple)):
          return [calendar_id2real_id(_id) for _id in ids]
  
  
  def real_id2calendar_id(record_id, date):
      return '%s-%s' % (record_id, date.strftime(VIRTUALID_DATETIME_FORMAT))
  
  
  def is_calendar_id(record_id):
      return len(str(record_id).split('-')) != 1
  
  
  class Contacts(models.Model):
      _name = 'calendar.contacts'
  
      user_id = fields.Many2one('res.users', 'Me', default=lambda self: self.env.user)
      partner_id = fields.Many2one('res.partner', 'Employee', required=True)
      active = fields.Boolean('Active', default=True)
  
      @api.model
      def unlink_from_partner_id(self, partner_id):
          return self.search([('partner_id', '=', partner_id)]).unlink()
  
  
  class Attendee(models.Model):
      """ Calendar Attendee Information """
  
      _name = 'calendar.attendee'
      _rec_name = 'cn'
      _description = 'Attendee information'
  
      def _default_access_token(self):
          return uuid.uuid4().hex
  
      STATE_SELECTION = [
          ('needsAction', 'Needs Action'),
          ('tentative', 'Uncertain'),
          ('declined', 'Declined'),
          ('accepted', 'Accepted'),
      ]
  
      state = fields.Selection(STATE_SELECTION, string='Status', readonly=True, default='needsAction',
          help="Status of the attendee's participation")
      cn = fields.Char('Common name', compute='_compute_common_name', store=True)
      partner_id = fields.Many2one('res.partner', 'Contact', readonly="True")
      email = fields.Char('Email', help="Email of Invited Person")
      availability = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True")
      access_token = fields.Char('Invitation Token', default=_default_access_token)
      event_id = fields.Many2one('calendar.event', 'Meeting linked', ondelete='cascade')
  
      @api.depends('partner_id', 'partner_id.name', 'email')
      def _compute_common_name(self):
          for attendee in self:
              attendee.cn = attendee.partner_id.name or attendee.email
  
      @api.onchange('partner_id')
      def _onchange_partner_id(self):
          """ Make entry on email and availability on change of partner_id field. """
          self.email = self.partner_id.email
  
      @api.model
      def create(self, values):
          if not values.get("email") and values.get("cn"):
              cnval = values.get("cn").split(':')
              email = filter(lambda x: x.__contains__('@'), cnval)  # TODO JEM : should be refactored
              values['email'] = email and email[0] or ''
              values['cn'] = values.get("cn")
          return super(Attendee, self).create(values)
  
      @api.multi
      def copy(self, default=None):
          raise UserError(_('You cannot duplicate a calendar attendee.'))
  
      @api.multi
      def _send_mail_to_attendees(self, template_xmlid, force_send=False):
          """ Send mail for event invitation to event attendees.
              :param template_xmlid: xml id of the email template to use to send the invitation
              :param force_send: If set to True, email will be sent to user himself. Usefull for alert, ...
          """
          res = False
  
          if self.env['ir.config_parameter'].get_param('calendar.block_mail') or self._context.get("no_mail_to_attendees"):
              return res
  
          calendar_view = self.env.ref('calendar.view_calendar_event_calendar')
          invitation_template = self.env.ref(template_xmlid)
  
          # get ics file for all meetings
          ics_files = self.mapped('event_id').get_ics_file()
  
          # prepare rendering context for mail template
          colors = {
              'needsAction': 'grey',
              'accepted': 'green',
              'tentative': '#FFFF00',
              'declined': 'red'
          }
          rendering_context = dict(self._context)
          rendering_context.update({
              'color': colors,
              'action_id': self.env['ir.actions.act_window'].search([('view_id', '=', calendar_view.id)], limit=1).id,
              'dbname': self._cr.dbname,
              'base_url': self.env['ir.config_parameter'].get_param('web.base.url', default='http://localhost:8069')
          })
          invitation_template = invitation_template.with_context(rendering_context)
  
          # send email with attachments
          mails_to_send = self.env['mail.mail']
          for attendee in self:
              if attendee.email or attendee.partner_id.email:
                  ics_file = ics_files[attendee.event_id.id]
                  mail_id = invitation_template.send_mail(attendee.id)
  
                  vals = {}
                  if ics_file:
                      vals['attachment_ids'] = [(0, 0, {'name': 'invitation.ics',
                                                        'datas_fname': 'invitation.ics',
                                                        'datas': str(ics_file).encode('base64')})]
                  vals['model'] = None  # We don't want to have the mail in the tchatter while in queue!
                  current_mail = self.env['mail.mail'].browse(mail_id)
                  current_mail.mail_message_id.write(vals)
                  mails_to_send |= current_mail
  
          if force_send and mails_to_send:
              res = mails_to_send.send()
  
          return res
  
      @api.multi
      def do_tentative(self):
          """ Makes event invitation as Tentative. """
          return self.write({'state': 'tentative'})
  
      @api.multi
      def do_accept(self):
          """ Marks event invitation as Accepted. """
          result = self.write({'state': 'accepted'})
          for attendee in self:
              attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.cn), subtype="calendar.subtype_invitation")
          return result
  
      @api.multi
      def do_decline(self):
          """ Marks event invitation as Declined. """
          res = self.write({'state': 'declined'})
          for attendee in self:
              attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.cn), subtype="calendar.subtype_invitation")
          return res
  
  
  class AlarmManager(models.AbstractModel):
  
      _name = 'calendar.alarm_manager'
  
      def get_next_potential_limit_alarm(self, alarm_type, seconds=None, partner_id=None):
          result = {}
          delta_request = """
              SELECT
                  rel.calendar_event_id, max(alarm.duration_minutes) AS max_delta,min(alarm.duration_minutes) AS min_delta
              FROM
                  calendar_alarm_calendar_event_rel AS rel
              LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id
              WHERE alarm.type = %s
              GROUP BY rel.calendar_event_id
          """
          base_request = """
                      SELECT
                          cal.id,
                          cal.start - interval '1' minute  * calcul_delta.max_delta AS first_alarm,
                          CASE
                              WHEN cal.recurrency THEN cal.final_date - interval '1' minute  * calcul_delta.min_delta
                              ELSE cal.stop - interval '1' minute  * calcul_delta.min_delta
                          END as last_alarm,
                          cal.start as first_event_date,
                          CASE
                              WHEN cal.recurrency THEN cal.final_date
                              ELSE cal.stop
                          END as last_event_date,
                          calcul_delta.min_delta,
                          calcul_delta.max_delta,
                          cal.rrule AS rule
                      FROM
                          calendar_event AS cal
                      RIGHT JOIN calcul_delta ON calcul_delta.calendar_event_id = cal.id
               """
  
          filter_user = """
                  RIGHT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
                      AND part_rel.res_partner_id = %s
          """
  
          # Add filter on alarm type
          tuple_params = (alarm_type,)
  
          # Add filter on partner_id
          if partner_id:
              base_request += filter_user
              tuple_params += (partner_id, )
  
          # Upper bound on first_alarm of requested events
          first_alarm_max_value = ""
          if seconds is None:
              # first alarm in the future + 3 minutes if there is one, now otherwise
              first_alarm_max_value = """
                  COALESCE((SELECT MIN(cal.start - interval '1' minute  * calcul_delta.max_delta)
                  FROM calendar_event cal
                  RIGHT JOIN calcul_delta ON calcul_delta.calendar_event_id = cal.id
                  WHERE cal.start - interval '1' minute  * calcul_delta.max_delta > now() at time zone 'utc'
              ) + interval '3' minute, now() at time zone 'utc')"""
          else:
              # now + given seconds
              first_alarm_max_value = "(now() at time zone 'utc' + interval '%s' second )"
              tuple_params += (seconds,)
  
          self._cr.execute("""
                      WITH calcul_delta AS (%s)
                      SELECT *
                          FROM ( %s WHERE cal.active = True ) AS ALL_EVENTS
                         WHERE ALL_EVENTS.first_alarm < %s
                           AND ALL_EVENTS.last_event_date > (now() at time zone 'utc')
                     """ % (delta_request, base_request, first_alarm_max_value), tuple_params)
  
          for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in self._cr.fetchall():
              result[event_id] = {
                  'event_id': event_id,
                  'first_alarm': first_alarm,
                  'last_alarm': last_alarm,
                  'first_meeting': first_meeting,
                  'last_meeting': last_meeting,
                  'min_duration': min_duration,
                  'max_duration': max_duration,
                  'rrule': rule
              }
  
          return result
  
      def do_check_alarm_for_one_date(self, one_date, event, event_maxdelta, in_the_next_X_seconds, alarm_type, after=False, missing=False):
          """ Search for some alarms in the interval of time determined by some parameters (after, in_the_next_X_seconds, ...)
              :param one_date: date of the event to check (not the same that in the event browse if recurrent)
              :param event: Event browse record
              :param event_maxdelta: biggest duration from alarms for this event
              :param in_the_next_X_seconds: looking in the future (in seconds)
              :param after: if not False: will return alert if after this date (date as string - todo: change in master)
              :param missing: if not False: will return alert even if we are too late
              :param notif: Looking for type notification
              :param mail: looking for type email
          """
          result = []
          # TODO: remove event_maxdelta and if using it
          if one_date - timedelta(minutes=(missing and 0 or event_maxdelta)) < datetime.now() + timedelta(seconds=in_the_next_X_seconds):  # if an alarm is possible for this date
              for alarm in event.alarm_ids:
                  if alarm.type == alarm_type and \
                      one_date - timedelta(minutes=(missing and 0 or alarm.duration_minutes)) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \
                          (not after or one_date - timedelta(minutes=alarm.duration_minutes) > fields.Datetime.from_string(after)):
                          alert = {
                              'alarm_id': alarm.id,
                              'event_id': event.id,
                              'notify_at': one_date - timedelta(minutes=alarm.duration_minutes),
                          }
                          result.append(alert)
          return result
  
      @api.model
      def get_next_mail(self):
          now = fields.Datetime.now()
          last_notif_mail = self.env['ir.config_parameter'].sudo().get_param('calendar.last_notif_mail', default=now)
  
          try:
              cron = self.env['ir.model.data'].sudo().get_object('calendar', 'ir_cron_scheduler_alarm')
          except ValueError:
              _logger.error("Cron for " + self._name + " can not be identified !")
              return False
  
          interval_to_second = {
              "weeks": 7 * 24 * 60 * 60,
              "days": 24 * 60 * 60,
              "hours": 60 * 60,
              "minutes": 60,
              "seconds": 1
          }
  
          if cron.interval_type not in interval_to_second:
              _logger.error("Cron delay can not be computed !")
              return False
  
          cron_interval = cron.interval_number * interval_to_second[cron.interval_type]
  
          all_meetings = self.get_next_potential_limit_alarm('email', seconds=cron_interval)
  
          for meeting in self.env['calendar.event'].browse(all_meetings.keys()):
              max_delta = all_meetings[meeting.id]['max_duration']
  
              if meeting.recurrency:
                  at_least_one = False
                  last_found = False
                  for one_date in current_event._get_recurrent_date_by_event():
                      in_date_format = one_date.replace(tzinfo=None)
                      last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, 0, 'email', after=last_notif_mail, missing=True)
                      for alert in last_found:
                          self.do_mail_reminder(alert)
                          at_least_one = True  # if it's the first alarm for this recurrent event
                      if at_least_one and not last_found:  # if the precedent event had an alarm but not this one, we can stop the search for this event
                          break
              else:
                  in_date_format = datetime.strptime(meeting.start, DEFAULT_SERVER_DATETIME_FORMAT)
                  last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, 0, 'email', after=last_notif_mail, missing=True)
                  for alert in last_found:
                      self.do_mail_reminder(alert)
          self.env['ir.config_parameter'].sudo().set_param('calendar.last_notif_mail', now)
  
      @api.model
      def get_next_notif(self):
          partner = self.env.user.partner_id
          all_notif = []
  
          if not partner:
              return []
  
          all_meetings = self.get_next_potential_limit_alarm('notification', partner_id=partner.id)
          time_limit = 3600 * 24  # return alarms of the next 24 hours
          for event_id in all_meetings:
              max_delta = all_meetings[event_id]['max_duration']
              meeting = self.env['calendar.event'].browse(event_id)
              if meeting.recurrency:
                  b_found = False
                  last_found = False
                  for one_date in current_event.get_recurrent_date_by_event():
                      in_date_format = one_date.replace(tzinfo=None)
                      last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, time_limit, 'notification', after=partner.calendar_last_notif_ack)
                      if last_found:
                          for alert in last_found:
                              all_notif.append(self.do_notif_reminder(alert))
                          if not b_found:  # if it's the first alarm for this recurrent event
                              b_found = True
                      if b_found and not last_found:  # if the precedent event had alarm but not this one, we can stop the search fot this event
                          break
              else:
                  in_date_format = fields.Datetime.from_string(meeting.start)
                  last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, time_limit, 'notification', after=partner.calendar_last_notif_ack)
                  if last_found:
                      for alert in last_found:
                          all_notif.append(self.do_notif_reminder(alert))
          return all_notif
  
      def do_mail_reminder(self, alert):
          meeting = self.env['calendar.event'].browse(alert['event_id'])
          alarm = self.env['calendar.alarm'].browse(alert['alarm_id'])
  
          result = False
          if alarm.type == 'email':
              result = meeting.attendee_ids._send_mail_to_attendees('calendar.calendar_template_meeting_reminder', force_send=True)
          return result
  
      def do_notif_reminder(self, alert):
          alarm = self.env['calendar.alarm'].browse(alert['alarm_id'])
          meeting = self.env['calendar.event'].browse(alert['event_id'])
  
          if alarm.type == 'notification':
              message = meeting.display_time
  
              delta = alert['notify_at'] - datetime.now()
              delta = delta.seconds + delta.days * 3600 * 24
  
              return {
                  'event_id': meeting.id,
                  'title': meeting.name,
                  'message': message,
                  'timer': delta,
                  'notify_at': fields.Datetime.to_string(alert['notify_at']),
              }
  
      def notify_next_alarm(self, partner_ids):
          """ Sends through the bus the next alarm of given partners """
          notifications = []
          users = self.env['res.users'].search([('partner_id', 'in', tuple(partner_ids))])
          for user in users:
              notif = self.sudo(user.id).get_next_notif()
              notifications.append([(self._cr.dbname, 'calendar.alarm', user.partner_id.id), notif])
          if len(notifications) > 0:
              self.env['bus.bus'].sendmany(notifications)
  
  
  class Alarm(models.Model):
      _name = 'calendar.alarm'
      _description = 'Event alarm'
  
      @api.depends('interval', 'duration')
      def _compute_duration_minutes(self):
          for alarm in self:
              if alarm.interval == "minutes":
                  alarm.duration_minutes = alarm.duration
              elif alarm.interval == "hours":
                  alarm.duration_minutes = alarm.duration * 60
              elif alarm.interval == "days":
                  alarm.duration_minutes = alarm.duration * 60 * 24
              else:
                  alarm.duration_minutes = 0
  
      _interval_selection = {'minutes': 'Minute(s)', 'hours': 'Hour(s)', 'days': 'Day(s)'}
  
      name = fields.Char('Name', required=True)
      type = fields.Selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True, default='email')
      duration = fields.Integer('Amount', required=True, default=1)
      interval = fields.Selection(list(_interval_selection.iteritems()), 'Unit', required=True, default='hours')
      duration_minutes = fields.Integer('Duration in minutes', compute='_compute_duration_minutes', store=True, help="Duration in minutes")
  
      @api.onchange('duration', 'interval')
      def _onchange_duration_interval(self):
          display_interval = self._interval_selection.get(self.interval, '')
          self.name = str(self.duration) + ' ' + display_interval
  
      def _update_cron(self):
          try:
              cron = self.env['ir.model.data'].sudo().get_object('calendar', 'ir_cron_scheduler_alarm')
          except ValueError:
              return False
          return cron.toggle(model=self._name, domain=[('type', '=', 'email')])
  
      @api.model
      def create(self, values):
          result = super(Alarm, self).create(values)
          self._update_cron()
          return result
  
      @api.multi
      def write(self, values):
          result = super(Alarm, self).write(values)
          self._update_cron()
          return result
  
      @api.multi
      def unlink(self):
          result = super(Alarm, self).unlink()
          self._update_cron()
          return result
  
  
  class MeetingType(models.Model):
  
      _name = 'calendar.event.type'
      _description = 'Meeting Type'
  
      name = fields.Char('Name', required=True)
  
      _sql_constraints = [
          ('name_uniq', 'unique (name)', "Tag name already exists !"),
      ]
  
  
  class Meeting(models.Model):
      """ Model for Calendar Event
  
          Special context keys :
              - `no_mail_to_attendees` : disabled sending email to attendees when creating/editing a meeting
      """
  
      _name = 'calendar.event'
      _description = "Event"
      _order = "id desc"
      _inherit = ["mail.thread", "ir.needaction_mixin"]
  
      @api.model
      def _default_partners(self):
          """ When active_model is res.partner, the current partners should be attendees """
          partners = self.env.user.partner_id
          active_id = self._context.get('active_id')
          if self._context.get('active_model') == 'res.partner' and active_id:
              if active_id not in partners.ids:
                  partners |= self.env['res.partner'].browse(active_id)
          return partners
  
      @api.multi
      def _get_recurrent_date_by_event(self):
          """ Get recurrent dates based on Rule string and all event where recurrent_id is child """
          self.ensure_one()
  
          def todate(date):
              val = parser.parse(''.join((re.compile('\d')).findall(date)))
              ## Dates are localized to saved timezone if any, else current timezone.
              if not val.tzinfo:
                  val = pytz.UTC.localize(val)
              return val.astimezone(timezone)
  
          timezone = pytz.timezone(self._context.get('tz') or 'UTC')
          startdate = pytz.UTC.localize(fields.Datetime.from_string(self.start))  # Add "+hh:mm" timezone
          if not startdate:
              startdate = datetime.now()
  
          # Convert the start date to saved timezone (or context tz) as it'll
          # define the correct hour/day asked by the user to repeat for recurrence.
          startdate = startdate.astimezone(timezone)  # transform "+hh:mm" timezone
          rset1 = rrule.rrulestr(str(self.rrule), dtstart=startdate, forceset=True)
          recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])
  
          for meeting in recurring_meetings:
              rset1._exdate.append(todate(meeting.recurrent_id_date))
          return [d.astimezone(pytz.UTC) for d in rset1]
  
      @api.multi
      def _get_recurrency_end_date(self):
          """ Return the last date a recurring event happens, according to its end_type. """
          self.ensure_one()
          data = self.read(['final_date', 'recurrency', 'rrule_type', 'count', 'end_type', 'stop'])[0]
  
          if not data.get('recurrency'):
              return False
  
          end_type = data.get('end_type')
          final_date = data.get('final_date')
          if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'stop']):
              count = data['count'] + 1
              delay, mult = {
                  'daily': ('days', 1),
                  'weekly': ('days', 7),
                  'monthly': ('months', 1),
                  'yearly': ('years', 1),
              }[data['rrule_type']]
  
              deadline = fields.Datetime.from_string(data['stop'])
              return deadline + relativedelta(**{delay: count * mult})
          return final_date
  
      @api.multi
      def _find_my_attendee(self):
          """ Return the first attendee where the user connected has been invited
              from all the meeting_ids in parameters.
          """
          self.ensure_one()
          for attendee in self.attendee_ids:
              if self.env.user.partner_id == attendee.partner_id:
                  return attendee
          return False
  
      @api.model
      def _get_date_formats(self):
          """ get current date and time format, according to the context lang
              :return: a tuple with (format date, format time)
          """
          lang = self._context.get("lang")
          lang_params = {}
          if lang:
              record_lang = self.env['res.lang'].search([("code", "=", lang)], limit=1)
              lang_params = {
                  'date_format': record_lang.date_format,
                  'time_format': record_lang.time_format
              }
  
          # formats will be used for str{f,p}time() which do not support unicode in Python 2, coerce to str
          format_date = lang_params.get("date_format", '%B-%d-%Y').encode('utf-8')
          format_time = lang_params.get("time_format", '%I-%M %p').encode('utf-8')
          return (format_date, format_time)
  
      @api.model
      def _get_recurrent_fields(self):
          return ['byday', 'recurrency', 'final_date', 'rrule_type', 'month_by',
                  'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa',
                  'su', 'day', 'week_list']
  
      @api.model
      def _get_display_time(self, start, stop, zduration, zallday):
          """ Return date and time (from to from) based on duration with timezone in string. Eg :
                  1) if user add duration for 2 hours, return : August-23-2013 at (04-30 To 06-30) (Europe/Brussels)
                  2) if event all day ,return : AllDay, July-31-2013
          """
          timezone = self._context.get('tz')
          if not timezone:
              timezone = self.env.user.partner_id.tz
          timezone = tools.ustr(timezone).encode('utf-8')  # make safe for str{p,f}time()
  
          # get date/time format according to context
          format_date, format_time = self.with_context(tz=timezone)._get_date_formats()
  
          # convert date and time into user timezone
          date = fields.Datetime.context_timestamp(self.with_context(tz=timezone), fields.Datetime.from_string(start))
          date_deadline = fields.Datetime.context_timestamp(self.with_context(tz=timezone), fields.Datetime.from_string(stop))
  
          # convert into string the date and time, using user formats
          date_str = date.strftime(format_date)
          time_str = date.strftime(format_time)
  
          if zallday:
              display_time = _("AllDay , %s") % (date_str)
          elif zduration < 24:
              duration = date + timedelta(hours=zduration)
              display_time = _("%s at (%s To %s) (%s)") % (date_str, time_str, duration.strftime(format_time), timezone)
          else:
              display_time = _("%s at %s To\n %s at %s (%s)") % (date_str, time_str, date_deadline.strftime(format_date), date_deadline.strftime(format_time), timezone)
          return display_time
  
      name = fields.Char('Meeting Subject', required=True, states={'done': [('readonly', True)]})
      state = fields.Selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, track_visibility='onchange', default='draft')
  
      is_attendee = fields.Boolean('Attendee', compute='_compute_attendee')
      attendee_status = fields.Selection(Attendee.STATE_SELECTION, string='Attendee Status', compute='_compute_attendee')
      display_time = fields.Char('Event Time', compute='_compute_display_time')
      display_start = fields.Date('Date', compute='_compute_display_start', store=True)
      start = fields.Datetime('Start', required=True, help="Start date of an event, without time for full days events")
      stop = fields.Datetime('Stop', required=True, help="Stop date of an event, without time for full days events")
  
      allday = fields.Boolean('All Day', states={'done': [('readonly', True)]}, default=False)
      start_date = fields.Date('Start Date', compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange')
      start_datetime = fields.Datetime('Start DateTime', compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange')
      stop_date = fields.Date('End Date', compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange')
      stop_datetime = fields.Datetime('End Datetime', compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange')  # old date_deadline
      duration = fields.Float('Duration', states={'done': [('readonly', True)]})
      description = fields.Text('Description', states={'done': [('readonly', True)]})
      privacy = fields.Selection([('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], 'Privacy', default='public', states={'done': [('readonly', True)]}, oldname="class")
      location = fields.Char('Location', states={'done': [('readonly', True)]}, track_visibility='onchange', help="Location of Event")
      show_as = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}, default='busy')
  
      # RECURRENCE FIELD
      rrule = fields.Char('Recurrent Rule', compute='_compute_rrule', inverse='_inverse_rrule', store=True)
      rrule_type = fields.Selection([
          ('daily', 'Day(s)'),
          ('weekly', 'Week(s)'),
          ('monthly', 'Month(s)'),
          ('yearly', 'Year(s)')
      ], string='Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval")
      recurrency = fields.Boolean('Recurrent', help="Recurrent Meeting")
      recurrent_id = fields.Integer('Recurrent ID')
      recurrent_id_date = fields.Datetime('Recurrent ID date')
      end_type = fields.Selection([
          ('count', 'Number of repetitions'),
          ('end_date', 'End date')
      ], string='Recurrence Termination', default='count')
      interval = fields.Integer(string='Repeat Every', default=1, help="Repeat every (Days/Week/Month/Year)")
      count = fields.Integer(string='Repeat', help="Repeat x times", default=1)
      mo = fields.Boolean('Mon')
      tu = fields.Boolean('Tue')
      we = fields.Boolean('Wed')
      th = fields.Boolean('Thu')
      fr = fields.Boolean('Fri')
      sa = fields.Boolean('Sat')
      su = fields.Boolean('Sun')
      month_by = fields.Selection([
          ('date', 'Date of month'),
          ('day', 'Day of month')
      ], string='Option', default='date', oldname='select1')
      day = fields.Integer('Date of month', default=1)
      week_list = fields.Selection([
          ('MO', 'Monday'),
          ('TU', 'Tuesday'),
          ('WE', 'Wednesday'),
          ('TH', 'Thursday'),
          ('FR', 'Friday'),
          ('SA', 'Saturday'),
          ('SU', 'Sunday')
      ], string='Weekday')
      byday = fields.Selection([
          ('1', 'First'),
          ('2', 'Second'),
          ('3', 'Third'),
          ('4', 'Fourth'),
          ('5', 'Fifth'),
          ('-1', 'Last')
      ], string='By day')
      final_date = fields.Date('Repeat Until')
  
      user_id = fields.Many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}, default=lambda self: self.env.user)
      color_partner_id = fields.Integer("Color index of creator", compute='_compute_color_partner', store=False)
  
      active = fields.Boolean('Active', default=True, help="If the active field is set to false, it will allow you to hide the event alarm information without removing it.")
      categ_ids = fields.Many2many('calendar.event.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags')
      attendee_ids = fields.One2many('calendar.attendee', 'event_id', 'Participant', ondelete='cascade')
      partner_ids = fields.Many2many('res.partner', 'calendar_event_res_partner_rel', string='Attendees', states={'done': [('readonly', True)]}, default=_default_partners)
      alarm_ids = fields.Many2many('calendar.alarm', 'calendar_alarm_calendar_event_rel', string='Reminders', ondelete="restrict", copy=False)
  
      @api.multi
      def _compute_attendee(self):
          for meeting in self:
              attendee = meeting._find_my_attendee()
              meeting.is_attendee = bool(attendee)
              meeting.attendee_status = attendee.state if attendee else 'needsAction'
  
      @api.multi
      def _compute_display_time(self):
          for meeting in self:
              meeting.display_time = self._get_display_time(meeting.start, meeting.stop, meeting.duration, meeting.allday)
  
      @api.multi
      @api.depends('allday', 'start_date', 'start_datetime')
      def _compute_display_start(self):
          for meeting in self:
              meeting.display_start = meeting.start_date if meeting.allday else meeting.start_datetime
  
      @api.multi
      @api.depends('allday', 'start', 'stop')
      def _compute_dates(self):
          """ Adapt the value of start_date(time)/stop_date(time) according to start/stop fields and allday. Also, compute
              the duration for not allday meeting ; otherwise the duration is set to zero, since the meeting last all the day.
          """
          for meeting in self:
              if meeting.allday:
                  meeting.start_date = meeting.start
                  meeting.start_datetime = False
                  meeting.stop_date = meeting.stop
                  meeting.stop_datetime = False
  
                  meeting.duration = 0
              else:
                  meeting.start_date = False
                  meeting.start_datetime = meeting.start
                  meeting.stop_date = False
                  meeting.stop_datetime = meeting.stop
  
                  diff = fields.Datetime.from_string(meeting.stop) - fields.Datetime.from_string(meeting.start)
                  if diff:
                      duration = float(diff.days) * 24 + (float(diff.seconds) / 3600)
                      meeting.duration = round(duration, 2)
  
      @api.multi
      def _inverse_dates(self):
          for meeting in self:
              if meeting.allday:
                  tz = pytz.timezone(self.env.user.tz) if self.env.user.tz else pytz.utc
  
                  enddate = fields.Datetime.from_string(meeting.stop_date)
                  enddate = tz.localize(enddate)
                  enddate = enddate.replace(hour=18)
                  enddate = enddate.astimezone(pytz.utc)
                  meeting.stop = fields.Datetime.to_string(enddate)
  
                  startdate = fields.Datetime.from_string(meeting.start_date)
                  startdate = tz.localize(startdate)  # Add "+hh:mm" timezone
                  startdate = startdate.replace(hour=8)  # Set 8 AM in localtime
                  startdate = startdate.astimezone(pytz.utc)  # Convert to UTC
                  meeting.start = fields.Datetime.to_string(startdate)
              else:
                  meeting.start = meeting.start_datetime
                  meeting.stop = meeting.stop_datetime
  
      @api.depends('byday', 'recurrency', 'final_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list')
      def _compute_rrule(self):
          """ Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
              :return dictionary of rrule value.
          """
          for meeting in self:
              if meeting.recurrency:
                  meeting.rrule = meeting._rrule_serialize()
              else:
                  meeting.rrule = ''
  
      @api.multi
      def _inverse_rrule(self):
          for meeting in self:
              if meeting.rrule:
                  data = self._rrule_default_values()
                  data['recurrency'] = True
                  data.update(self._rrule_parse(meeting.rrule, data, meeting.start))
                  meeting.update(data)
  
      @api.multi
      def _compute_color_partner(self):
          for meeting in self:
              meeting.color_partner_id = meeting.user_id.partner_id.id
  
      @api.constrains('start_datetime', 'stop_datetime', 'start_date', 'stop_date')
      def _check_closing_date(self):
          for meeting in self:
              if meeting.start_datetime and meeting.stop_datetime and meeting.stop_datetime < meeting.start_datetime:
                  raise ValidationError(_('Ending datetime cannot be set before starting datetime.'))
              if meeting.start_date and meeting.stop_date and meeting.stop_date < meeting.start_date:
                  raise ValidationError(_('Ending date cannot be set before starting date.'))
  
      @api.onchange('start_datetime', 'duration')
      def _onchange_duration(self):
          if self.start_datetime:
              start = fields.Datetime.from_string(self.start_datetime)
              self.start = self.start_datetime
              self.stop = fields.Datetime.to_string(start + timedelta(hours=self.duration))
  
      ####################################################
      # Calendar Business, Reccurency, ...
      ####################################################
  
      @api.multi
      def get_ics_file(self):
          """ Returns iCalendar file for the event invitation.
              :returns a dict of .ics file content for each meeting
          """
          result = {}
  
          def ics_datetime(idate, allday=False):
              if idate:
                  if allday:
                      return fields.Date.from_string(idate)
                  else:
                      return fields.Datetime.from_string(idate).replace(tzinfo=pytz.timezone('UTC'))
              return False
  
          try:
              # FIXME: why isn't this in CalDAV?
              import vobject
          except ImportError:
              return result
  
          for meeting in self:
              cal = vobject.iCalendar()
              event = cal.add('vevent')
  
              if not meeting.start or not meeting.stop:
                  raise UserError(_("First you have to specify the date of the invitation."))
              event.add('created').value = ics_datetime(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
              event.add('dtstart').value = ics_datetime(meeting.start, meeting.allday)
              event.add('dtend').value = ics_datetime(meeting.stop, meeting.allday)
              event.add('summary').value = meeting.name
              if meeting.description:
                  event.add('description').value = meeting.description
              if meeting.location:
                  event.add('location').value = meeting.location
              if meeting.rrule:
                  event.add('rrule').value = meeting.rrule
  
              if meeting.alarm_ids:
                  for alarm in meeting.alarm_ids:
                      valarm = event.add('valarm')
                      interval = alarm.interval
                      duration = alarm.duration
                      trigger = valarm.add('TRIGGER')
                      trigger.params['related'] = ["START"]
                      if interval == 'days':
                          delta = timedelta(days=duration)
                      elif interval == 'hours':
                          delta = timedelta(hours=duration)
                      elif interval == 'minutes':
                          delta = timedelta(minutes=duration)
                      trigger.value = delta
                      valarm.add('DESCRIPTION').value = alarm.name or 'Odoo'
              for attendee in meeting.attendee_ids:
                  attendee_add = event.add('attendee')
                  attendee_add.value = 'MAILTO:' + (attendee.email or '')
              result[meeting.id] = cal.serialize()
  
          return result
  
      @api.multi
      def create_attendees(self):
          current_user = self.env.user
          result = {}
          for meeting in self:
              alreay_meeting_partners = meeting.attendee_ids.mapped('partner_id')
              meeting_attendees = self.env['calendar.attendee']
              meeting_partners = self.env['res.partner']
              for partner in meeting.partner_ids.filtered(lambda partner: partner not in alreay_meeting_partners):
                  values = {
                      'partner_id': partner.id,
                      'email': partner.email,
                      'event_id': meeting.id,
                  }
  
                  # current user don't have to accept his own meeting
                  if partner == self.env.user.partner_id:
                      values['state'] = 'accepted'
  
                  attendee = self.env['calendar.attendee'].create(values)
  
                  meeting_attendees |= attendee
                  meeting_partners |= partner
  
                  if current_user.email != partner.email:
                      attendee._send_mail_to_attendees('calendar.calendar_template_meeting_invitation')
  
              if meeting_attendees:
                  meeting.write({'attendee_ids': [(4, attendee.id) for attendee in meeting_attendees]})
              if meeting_partners:
                  meeting.message_subscribe(partner_ids=meeting_partners.ids)
  
              # We remove old attendees who are not in partner_ids now.
              all_partners = meeting.partner_ids
              all_partner_attendees = meeting.attendee_ids.mapped('partner_id')
              old_attendees = meeting.attendee_ids
              partners_to_remove = all_partner_attendees + meeting_partners - all_partners
  
              attendees_to_remove = self.env["calendar.attendee"]
              if partners_to_remove:
                  attendees_to_remove = self.env["calendar.attendee"].search([('partner_id', 'in', partners_to_remove.ids), ('event_id', '=', meeting.id)]).ids
                  attendees_to_remove.unlink()
  
              result[meeting.id] = {
                  'new_attendees': meeting_attendees,
                  'old_attendees': old_attendees,
                  'removed_attendees': attendees_to_remove,
                  'removed_partners': partners_to_remove
              }
          return result
  
      @api.multi
      def get_search_fields(self, order_fields, r_date=None):
          sort_fields = {}
          for field in order_fields:
              if field == 'id' and r_date:
                  sort_fields[field] = real_id2calendar_id(self.id, r_date)
              else:
                  sort_fields[field] = self[field]
                  if isinstance(self[field], models.BaseModel):
                      name_get = self[field].name_get()
                      if len(name_get) and len(name_get[0]) >= 2:
                          sort_fields[field] = name_get[0][1]
          if r_date:
              sort_fields['sort_start'] = r_date.strftime(VIRTUALID_DATETIME_FORMAT)
          else:
              display_start = self.display_start
              sort_fields['sort_start'] = display_start.replace(' ', '').replace('-', '') if display_start else False
          return sort_fields
  
      @api.multi
      def get_recurrent_ids(self, domain, order=None):
          """ Gives virtual event ids for recurring events. This method gives ids of dates
              that comes between start date and end date of calendar views
              :param order:   The fields (comma separated, format "FIELD {DESC|ASC}") on which
                              the events should be sorted
          """
          if order:
              order_fields = [field.split()[0] for field in order.split(',')]
          else:
              # fallback on self._order defined on the model
              order_fields = [field.split()[0] for field in self._order.split(',')]
  
          if 'id' not in order_fields:
              order_fields.append('id')
  
          result_data = []
          result = []
          for meeting in self:
              if not meeting.recurrency or not meeting.rrule:
                  result.append(meeting.id)
                  result_data.append(meeting.get_search_fields(order_fields))
                  continue
              rdates = meeting._get_recurrent_date_by_event()
  
              for r_date in rdates:
                  # fix domain evaluation
                  # step 1: check date and replace expression by True or False, replace other expressions by True
                  # step 2: evaluation of & and |
                  # check if there are one False
                  pile = []
                  ok = True
                  for arg in domain:
                      if str(arg[0]) in ('start', 'stop', 'final_date'):
                          if (arg[1] == '='):
                              ok = r_date.strftime('%Y-%m-%d') == arg[2]
                          if (arg[1] == '>'):
                              ok = r_date.strftime('%Y-%m-%d') > arg[2]
                          if (arg[1] == '<'):
                              ok = r_date.strftime('%Y-%m-%d') < arg[2]
                          if (arg[1] == '>='):
                              ok = r_date.strftime('%Y-%m-%d') >= arg[2]
                          if (arg[1] == '<='):
                              ok = r_date.strftime('%Y-%m-%d') <= arg[2]
                          pile.append(ok)
                      elif str(arg) == str('&') or str(arg) == str('|'):
                          pile.append(arg)
                      else:
                          pile.append(True)
                  pile.reverse()
                  new_pile = []
                  for item in pile:
                      if not isinstance(item, basestring):
                          res = item
                      elif str(item) == str('&'):
                          first = new_pile.pop()
                          second = new_pile.pop()
                          res = first and second
                      elif str(item) == str('|'):
                          first = new_pile.pop()
                          second = new_pile.pop()
                          res = first or second
                      new_pile.append(res)
  
                  if [True for item in new_pile if not item]:
                      continue
                  result_data.append(meeting.get_search_fields(order_fields, r_date=r_date))
  
          if order_fields:
              uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
  
              def comparer(left, right):
                  for fn, mult in comparers:
                      result = cmp(fn(left), fn(right))
                      if result:
                          return mult * result
                  return 0
  
              sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
              sort_params = uniq([comp if comp not in ['start', 'start_date', 'start_datetime'] else 'sort_start' for comp in sort_params])
              sort_params = uniq([comp if comp not in ['-start', '-start_date', '-start_datetime'] else '-sort_start' for comp in sort_params])
              comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
              ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
  
          return ids
  
      @api.multi
      def _rrule_serialize(self):
          """ Compute rule string according to value type RECUR of iCalendar
              :return: string containing recurring rule (empty if no rule)
          """
          if self.interval and self.interval < 0:
              raise UserError(_('interval cannot be negative.'))
          if self.count and self.count <= 0:
              raise UserError(_('Event recurrence interval cannot be negative.'))
  
          def get_week_string(freq):
              weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
              if freq == 'weekly':
                  byday = [field.upper() for field in weekdays if self[field]]
                  if byday:
                      return ';BYDAY=' + ','.join(byday)
              return ''
  
          def get_month_string(freq):
              if freq == 'monthly':
                  if self.month_by == 'date' and (self.day < 1 or self.day > 31):
                      raise UserError(_("Please select a proper day of the month."))
  
                  if self.month_by == 'day':  # Eg : Second Monday of the month
                      return ';BYDAY=' + self.byday + self.week_list
                  elif self.month_by == 'date':  # Eg : 16th of the month
                      return ';BYMONTHDAY=' + str(self.day)
              return ''
  
          def get_end_date():
              end_date_new = ''.join((re.compile('\d')).findall(self.final_date)) + 'T235959Z' if self.final_date else False
              return (self.end_type == 'count' and (';COUNT=' + str(self.count)) or '') +\
                  ((end_date_new and self.end_type == 'end_date' and (';UNTIL=' + end_date_new)) or '')
  
          freq = self.rrule_type  # day/week/month/year
          result = ''
          if freq:
              interval_srting = self.interval and (';INTERVAL=' + str(self.interval)) or ''
              result = 'FREQ=' + freq.upper() + get_week_string(freq) + interval_srting + get_end_date() + get_month_string(freq)
          return result
  
      def _rrule_default_values(self):
          return {
              'byday': False,
              'recurrency': False,
              'final_date': False,
              'rrule_type': False,
              'month_by': False,
              'interval': 0,
              'count': False,
              'end_type': False,
              'mo': False,
              'tu': False,
              'we': False,
              'th': False,
              'fr': False,
              'sa': False,
              'su': False,
              'day': False,
              'week_list': False
          }
  
      def _rrule_parse(self, rule_str, data, date_start):
          day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
          rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
          rule = rrule.rrulestr(rule_str, dtstart=fields.Datetime.from_string(date_start))
  
          if rule._freq > 0 and rule._freq < 4:
              data['rrule_type'] = rrule_type[rule._freq]
          data['count'] = rule._count
          data['interval'] = rule._interval
          data['final_date'] = rule._until and rule._until.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
          #repeat weekly
          if rule._byweekday:
              for i in xrange(0, 7):
                  if i in rule._byweekday:
                      data[day_list[i]] = True
              data['rrule_type'] = 'weekly'
          #repeat monthly by nweekday ((weekday, weeknumber), )
          if rule._bynweekday:
              data['week_list'] = day_list[rule._bynweekday[0][0]].upper()
              data['byday'] = str(rule._bynweekday[0][1])
              data['month_by'] = 'day'
              data['rrule_type'] = 'monthly'
  
          if rule._bymonthday:
              data['day'] = rule._bymonthday[0]
              data['month_by'] = 'date'
              data['rrule_type'] = 'monthly'
  
          #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
          if rule._bymonth:
              data['interval'] = data['interval'] * 12
  
          #FIXEME handle forever case
          #end of recurrence
          #in case of repeat for ever that we do not support right now
          if not (data.get('count') or data.get('final_date')):
              data['count'] = 100
          if data.get('count'):
              data['end_type'] = 'count'
          else:
              data['end_type'] = 'end_date'
          return data
  
      @api.multi
      def get_interval(self, interval, tz=None):
          """ Format and localize some dates to be used in email templates
              :param string interval: Among 'day', 'month', 'dayname' and 'time' indicating the desired formatting
              :param string tz: Timezone indicator (optional)
              :return unicode: Formatted date or time (as unicode string, to prevent jinja2 crash)
          """
          self.ensure_one()
          date = fields.Datetime.from_string(self.start)
  
          if tz:
              timezone = pytz.timezone(tz or 'UTC')
              date = date.replace(tzinfo=pytz.timezone('UTC')).astimezone(timezone)
  
          if interval == 'day':
              # Day number (1-31)
              result = unicode(date.day)
  
          elif interval == 'month':
              # Localized month name and year
              result = babel.dates.format_date(date=date, format='MMMM y', locale=self._context.get('lang', 'en_US'))
  
          elif interval == 'dayname':
              # Localized day name
              result = babel.dates.format_date(date=date, format='EEEE', locale=self._context.get('lang', 'en_US'))
  
          elif interval == 'time':
              # Localized time
              dummy, format_time = self._get_date_formats()
              result = tools.ustr(date.strftime(format_time + " %Z"))
  
          return result
  
      @api.multi
      def get_display_time_tz(self, tz=False):
          """ get the display_time of the meeting, forcing the timezone. This method is called from email template, to not use sudo(). """
          self.ensure_one()
          if tz:
              self = self.with_context(tz=tz)
          return self._get_display_time(self.start, self.stop, self.duration, self.allday)
  
      @api.multi
      def detach_recurring_event(self, values=None):
          """ Detach a virtual recurring event by duplicating the original and change reccurent values
              :param values : dict of value to override on the detached event
          """
          if not values:
              values = {}
  
          real_id = calendar_id2real_id(self.id)
          meeting_origin = self.browse(real_id)
  
          data = self.read(['allday', 'start', 'stop', 'rrule', 'duration'])[0]
          data['start_date' if data['allday'] else 'start_datetime'] = data['start']
          data['stop_date' if data['allday'] else 'stop_datetime'] = data['stop']
          if data.get('rrule'):
              data.update(
                  values,
                  recurrent_id=real_id,
                  recurrent_id_date=data.get('start'),
                  rrule_type=False,
                  rrule='',
                  recurrency=False,
                  final_date=datetime.strptime(data.get('start'), DEFAULT_SERVER_DATETIME_FORMAT if data['allday'] else DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=values.get('duration', False) or data.get('duration'))
              )
  
              # do not copy the id
              if data.get('id'):
                  del data['id']
              return meeting_origin.copy(default=data)
  
      @api.multi
      def action_detach_recurring_event(self):
          meeting = self.detach_recurring_event()
          return {
              'type': 'ir.actions.act_window',
              'res_model': 'calendar.event',
              'view_mode': 'form',
              'res_id': meeting.id,
              'target': 'current',
              'flags': {'form': {'action_buttons': True, 'options': {'mode': 'edit'}}}
          }
  
      @api.multi
      def action_sendmail(self):
          email = self.env.user.email
          if email:
              for meeting in self:
                  meeting.attendee_ids._send_mail_to_attendees('calendar.calendar_template_meeting_invitation')
          return True
  
      ####################################################
      # Messaging
      ####################################################
  
      # shows events of the day for this user
      @api.model
      def _needaction_domain_get(self):
          return [
              ('stop', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')),
              ('start', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 00:00:00')),
              ('user_id', '=', self.env.user.id),
          ]
  
      @api.multi
      def _get_message_unread(self):
          id_map = {x: calendar_id2real_id(x) for x in self.ids}
          real = self.browse(set(id_map.values()))
          super(Meeting, real)._get_message_unread()
          for event in self:
              if event.id == id_map[event.id]:
                  continue
              rec = self.browse(id_map[event.id])
              event.message_unread_counter = rec.message_unread_counter
              event.message_unread = rec.message_unread
  
      @api.multi
      def _get_message_needaction(self):
          id_map = {x: calendar_id2real_id(x) for x in self.ids}
          real = self.browse(set(id_map.values()))
          super(Meeting, real)._get_message_needaction()
          for event in self:
              if event.id == id_map[event.id]:
                  continue
              rec = self.browse(id_map[event.id])
              event.message_needaction_counter = rec.message_needaction_counter
              event.message_needaction = rec.message_needaction
  
      @api.multi
      @api.returns('self', lambda value: value.id)
      def message_post(self, **kwargs):
          thread_id = self.id
          if isinstance(self.id, basestring):
              thread_id = get_real_ids(self.id)
          if self.env.context.get('default_date'):
              context = dict(self.env.context)
              del context['default_date']
              self = self.with_context(context)
          return super(Meeting, self.browse(thread_id)).message_post(**kwargs)
  
      @api.multi
      def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True):
          records = self.browse(get_real_ids(self.ids))
          return super(Meeting, records).message_subscribe(
              partner_ids=partner_ids,
              channel_ids=channel_ids,
              subtype_ids=subtype_ids,
              force=force)
  
      @api.multi
      def message_unsubscribe(self, partner_ids=None, channel_ids=None):
          records = self.browse(get_real_ids(self.ids))
          return super(Meeting, records).message_unsubscribe(partner_ids=partner_ids, channel_ids=channel_ids)
  
      ####################################################
      # ORM Overrides
      ####################################################
  
      @api.multi
      def get_metadata(self):
          real = self.browse(set({x: calendar_id2real_id(x) for x in self.ids}.values()))
          return super(Meeting, real).get_metadata()
  
      @api.model
      def _name_search(self, name='', args=None, operator='ilike', limit=100, name_get_uid=None):
          for arg in args:
              if arg[0] == 'id':
                  for n, calendar_id in enumerate(arg[2]):
                      if isinstance(calendar_id, basestring):
                          arg[2][n] = calendar_id.split('-')[0]
          return super(Meeting, self)._name_search(name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid)
  
      @api.multi
      def write(self, values):
          # process events one by one
          for meeting in self:
              # special write of complex IDS
              real_ids = []
              new_ids = []
              if not is_calendar_id(meeting.id):
                  real_ids = [int(meeting.id)]
              else:
                  real_event_id = calendar_id2real_id(meeting.id)
  
                  # if we are setting the recurrency flag to False or if we are only changing fields that
                  # should be only updated on the real ID and not on the virtual (like message_follower_ids):
                  # then set real ids to be updated.
                  blacklisted = any(key in values for key in ('start', 'stop', 'active'))
                  if not values.get('recurrency', True) or not blacklisted:
                      real_ids = [real_event_id]
                  else:
                      data = meeting.read(['start', 'stop', 'rrule', 'duration'])[0]
                      if data.get('rrule'):
                          new_ids = meeting.with_context(dont_notify=True)._detach_one_event(values).ids  # to prevent multiple notify_next_alarm
  
              new_meetings = self.browse(new_ids)
              real_meetings = self.browse(real_ids)
              all_meetings = real_meetings + new_meetings
              super(Meeting, real_meetings).write(values)
  
              # set end_date for calendar searching
              if values.get('recurrency') and values.get('end_type', 'count') in ('count', unicode('count')) and \
                      (values.get('rrule_type') or values.get('count') or values.get('start') or values.get('stop')):
                  for real_meeting in real_meetings:
                      final_date = real_meeting._get_recurrency_end_date()
                      super(Meeting, real_meeting).write({'final_date': final_date})
  
              attendees_create = False
              if values.get('partner_ids', False):
                  attendees_create = all_meetings.with_context(dont_notify=True).create_attendees()  # to prevent multiple notify_next_alarm
  
              # Notify attendees if there is an alarm on the modified event, or if there was an alarm
              # that has just been removed, as it might have changed their next event notification
              if not self._context.get('dont_notify'):
                  if len(meeting.alarm_ids) > 0 or values.get('alarm_ids'):
                      partners_to_notify = meeting.partner_ids.ids
                      event_attendees_changes = attendees_create and attendees_create[real_ids[0]]
                      if event_attendees_changes:
                          partners_to_notify.update(event_attendees_changes['removed_partners'].ids)
                      self.env['calendar.alarm_manager'].notify_next_alarm(partners_to_notify)
  
              if (values.get('start_date') or values.get('start_datetime')) and values.get('active', True):
                  for current_meeting in all_meetings:
                      if attendees_create:
                          attendees_create = attendees_create[current_meeting.id]
                          attendee_to_email = attendees_create['old_attendees'] - attendees_create['removed_attendees']
                      else:
                          attendee_to_email = current_meeting.attendee_ids
  
                      if attendee_to_email:
                          attendee_to_email._send_mail_to_attendees('calendar.calendar_template_meeting_changedate')
          return True
  
      @api.model
      def create(self, values):
          if not 'user_id' in values:  # Else bug with quick_create when we are filter on an other user
              values['user_id'] = self.env.user.id
  
          meeting = super(Meeting, self).create(values)
  
          final_date = meeting._get_recurrency_end_date()
          # `dont_notify=True` in context to prevent multiple notify_next_alarm
          meeting.with_context(dont_notify=True).write({'final_date': final_date})
          meeting.with_context(dont_notify=True).create_attendees()
  
          # Notify attendees if there is an alarm on the created event, as it might have changed their
          # next event notification
          if not self._context.get('dont_notify'):
              if len(meeting.alarm_ids) > 0:
                  self.env['calendar.alarm_manager'].notify_next_alarm(meeting.partner_ids.ids)
          return meeting
  
      @api.multi
      def export_data(self, fields_to_export, raw_data=False):
          """ Override to convert virtual ids to ids """
          records = self.browse(set(get_real_ids(self.ids)))
          return super(Meeting, records).export_data(fields_to_export, raw_data)
  
      @api.model
      def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
          if 'date' in groupby:
              raise UserError(_('Group by date is not supported, use the calendar view instead.'))
          return super(Meeting, self.with_context(virtual_id=False)).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
  
      @api.multi
      def read(self, fields=None, load='_classic_read'):
          fields2 = fields and fields[:] or None
          EXTRAFIELDS = ('privacy', 'user_id', 'duration', 'allday', 'start', 'start_date', 'start_datetime', 'rrule')
          for f in EXTRAFIELDS:
              if fields and (f not in fields):
                  fields2.append(f)
  
          select = map(lambda x: (x, calendar_id2real_id(x)), self.ids)
          real_events = self.browse([real_id for calendar_id, real_id in select])
          real_data = super(Meeting, real_events).read(fields=fields2, load=load)
          real_data = dict((d['id'], d) for d in real_data)
  
          result = []
          for calendar_id, real_id in select:
              res = real_data[real_id].copy()
              ls = calendar_id2real_id(calendar_id, with_date=res and res.get('duration', 0) > 0 and res.get('duration') or 1)
              if not isinstance(ls, (basestring, int, long)) and len(ls) >= 2:
                  res['start'] = ls[1]
                  res['stop'] = ls[2]
  
                  if res['allday']:
                      res['start_date'] = ls[1]
                      res['stop_date'] = ls[2]
                  else:
                      res['start_datetime'] = ls[1]
                      res['stop_datetime'] = ls[2]
  
                  if 'display_time' in fields:
                      res['display_time'] = self._get_display_time(ls[1], ls[2], res['duration'], res['allday'])
  
              res['id'] = calendar_id
              result.append(res)
  
          for r in result:
              if r['user_id']:
                  user_id = type(r['user_id']) in (tuple, list) and r['user_id'][0] or r['user_id']
                  if user_id == self.env.user.id:
                      continue
              if r['privacy'] == 'private':
                  for f in r.keys():
                      recurrent_fields = self._get_recurrent_fields()
                      public_fields = list(set(recurrent_fields + ['id', 'allday', 'start', 'stop', 'display_start', 'display_stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule']))
                      if f not in public_fields:
                          if isinstance(r[f], list):
                              r[f] = []
                          else:
                              r[f] = False
                      if f == 'name':
                          r[f] = _('Busy')
  
          for r in result:
              for k in EXTRAFIELDS:
                  if (k in r) and (fields and (k not in fields)):
                      del r[k]
          return result
  
      @api.multi
      def unlink(self, can_be_deleted=True):
          # Get concerned attendees to notify them if there is an alarm on the unlinked events,
          # as it might have changed their next event notification
          events = self.search([('id', 'in', self.ids), ('alarm_ids', '!=', False)])
          partner_ids = events.mapped('partner_ids').ids
  
          records_to_exclude = self.env['calendar.event']
          records_to_unlink = self.env['calendar.event']
  
          for meeting in self:
              if can_be_deleted and not is_calendar_id(meeting.id):  # if  ID REAL
                  if meeting.recurrent_id:
                      records_to_exclude |= meeting
                  else:
                      # int() required because 'id' from calendar view is a string, since it can be calendar virtual id
                      records_to_unlink |= self.browse(int(meeting.id))
              else:
                  records_to_exclude |= meeting
  
          result = False
          if records_to_unlink:
              result = super(Meeting, records_to_unlink).unlink()
          if records_to_exclude:
              result = records_to_exclude.with_context(dont_notify=True).write({'active': False})
  
          # Notify the concerned attendees (must be done after removing the events)
          self.env['calendar.alarm_manager'].notify_next_alarm(partner_ids)
          return result
  
      @api.model
      def search(self, args, offset=0, limit=0, order=None, count=False):
          if self._context.get('mymeetings'):
              args += [('partner_ids', 'in', self.env.user.partner_id.ids)]
  
          new_args = []
          for arg in args:
              new_arg = arg
              if arg[0] in ('stop_date', 'stop_datetime', 'stop',) and arg[1] == ">=":
                  if self._context.get('virtual_id', True):
                      new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])]
              elif arg[0] == "id":
                  new_arg = (arg[0], arg[1], get_real_ids(arg[2]))
              new_args.append(new_arg)
  
          if not self._context.get('virtual_id', True):
              return super(Meeting, self).search(new_args, offset=offset, limit=limit, order=order, count=count)
  
          # offset, limit, order and count must be treated separately as we may need to deal with virtual ids
          events = super(Meeting, self).search(new_args, offset=0, limit=0, order=None, count=False)
          events = self.browse(events.get_recurrent_ids(args, order=order))
          if count:
              return len(events)
          elif limit:
              return events[offset: offset + limit]
          return events
  
      @api.multi
      def copy(self, default=None):
          self.ensure_one()
          default = default or {}
          return super(Meeting, self.browse(calendar_id2real_id(self.id))).copy(default)
  