# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ############################################################################## from datetime import datetime, timedelta, date from dateutil import parser from dateutil import rrule from dateutil.relativedelta import relativedelta from openerp.osv import fields, osv from openerp.tools.translate import _ import pytz import re import time from openerp import tools, SUPERUSER_ID import openerp.service.report months = { 1: "January", 2: "February", 3: "March", 4: "April", \ 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \ 10: "October", 11: "November", 12: "December" } def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None): """ Get recurrent dates based on Rule string considering exdate and start date. @param rrulestring: rulestring @param exdate: list of exception dates for rrule @param startdate: startdate for computing recurrent dates @return: list of Recurrent dates """ def todate(date): val = parser.parse(''.join((re.compile('\d')).findall(date))) return val if not startdate: startdate = datetime.now() if not exdate: exdate = [] rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True) for date in exdate: datetime_obj = todate(date) rset1._exdate.append(datetime_obj) if exrule: rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate)) return list(rset1) def base_calendar_id2real_id(base_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 base_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 + base_calendar_id @return: real event id """ if base_calendar_id and isinstance(base_calendar_id, (str, unicode)): res = base_calendar_id.split('-') if len(res) >= 2: real_id = res[0] if with_date: real_date = time.strftime("%Y-%m-%d %H:%M:%S", \ time.strptime(res[1], "%Y%m%d%H%M%S")) start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S") end = start + timedelta(hours=with_date) return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S")) return int(real_id) return base_calendar_id and int(base_calendar_id) or base_calendar_id def get_real_ids(ids): if isinstance(ids, (str, int, long)): return base_calendar_id2real_id(ids) if isinstance(ids, (list, tuple)): res = [] for id in ids: res.append(base_calendar_id2real_id(id)) return res def real_id2base_calendar_id(real_id, recurrent_date): """ Convert a real event id (type int) into a "virtual/recurring event id" (type string). E.g. real event id is 1 and recurrent_date is set to 01-12-2009 10:00:00, so it will return 1-20091201100000. @param real_id: real event id @param recurrent_date: real event recurrent date @return: string containing the real id and the recurrent date """ if real_id and recurrent_date: recurrent_date = time.strftime("%Y%m%d%H%M%S", \ time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S")) return '%d-%s' % (real_id, recurrent_date) return real_id def _links_get(self, cr, uid, context=None): """ Get request link. @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param context: a standard dictionary for contextual values @return: list of dictionary which contain object and name and id """ obj = self.pool.get('res.request.link') ids = obj.search(cr, uid, []) res = obj.read(cr, uid, ids, ['object', 'name'], context=context) return [(r['object'], r['name']) for r in res] html_invitation = """ %(name)s
Hello,
You are invited for %(company)s Event.
Below are the details of event. Hours and dates expressed in %(timezone)s time.

%(name)s

Start Date
: %(start_date)s
End Date
: %(end_date)s
Description : %(description)s
Location
: %(location)s
Event Attendees
:
%(attendees)s
From:
%(user)s
--------------------------
%(sign)s
""" class calendar_attendee(osv.osv): """ Calendar Attendee Information """ _name = 'calendar.attendee' _description = 'Attendee information' _rec_name = 'cutype' __attribute__ = {} def _get_address(self, name=None, email=None): """ Gives email information in ical CAL-ADDRESS type format. @param name: name for CAL-ADDRESS value @param email: email address for CAL-ADDRESS value """ if name and email: name += ':' return (name or '') + (email and ('MAILTO:' + email) or '') def _compute_data(self, cr, uid, ids, name, arg, context=None): """ Compute data on function fields for attendee values. @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param name: name of field @param context: a standard dictionary for contextual values @return: dictionary of form {id: {'field Name': value'}} """ name = name[0] result = {} for attdata in self.browse(cr, uid, ids, context=context): id = attdata.id result[id] = {} if name == 'sent_by': if not attdata.sent_by_uid: result[id][name] = '' continue else: result[id][name] = self._get_address(attdata.sent_by_uid.name, \ attdata.sent_by_uid.email) if name == 'cn': if attdata.user_id: result[id][name] = attdata.user_id.name elif attdata.partner_id: result[id][name] = attdata.partner_id.name or False else: result[id][name] = attdata.email or '' if name == 'delegated_to': todata = [] for child in attdata.child_ids: if child.email: todata.append('MAILTO:' + child.email) result[id][name] = ', '.join(todata) if name == 'delegated_from': fromdata = [] for parent in attdata.parent_ids: if parent.email: fromdata.append('MAILTO:' + parent.email) result[id][name] = ', '.join(fromdata) if name == 'event_date': if attdata.ref: result[id][name] = attdata.ref.date else: result[id][name] = False if name == 'event_end_date': if attdata.ref: result[id][name] = attdata.ref.date_deadline else: result[id][name] = False if name == 'sent_by_uid': if attdata.ref: result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name) else: result[id][name] = uid if name == 'language': user_obj = self.pool.get('res.users') lang = user_obj.read(cr, uid, uid, ['lang'], context=context)['lang'] result[id][name] = lang.replace('_', '-') if lang else False return result def _links_get(self, cr, uid, context=None): """ Get request link for ref field in calendar attendee. @param cr: the current row, from the database cursor @param uid: the current user's id for security checks @param context: A standard dictionary for contextual values @return: list of dictionary which contain object and name and id """ obj = self.pool.get('res.request.link') ids = obj.search(cr, uid, []) res = obj.read(cr, uid, ids, ['object', 'name'], context=context) return [(r['object'], r['name']) for r in res] def _lang_get(self, cr, uid, context=None): """ Get language for language selection field. @param cr: the current row, from the database cursor @param uid: the current user's id for security checks @param context: a standard dictionary for contextual values @return: list of dictionary which contain code and name and id """ obj = self.pool.get('res.lang') ids = obj.search(cr, uid, []) res = obj.read(cr, uid, ids, ['code', 'name'], context=context) res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res] return res _columns = { 'cutype': fields.selection([('individual', 'Individual'), \ ('group', 'Group'), ('resource', 'Resource'), \ ('room', 'Room'), ('unknown', 'Unknown') ], \ 'Invite Type', help="Specify the type of Invitation"), 'member': fields.char('Member', size=124, help="Indicate the groups that the attendee belongs to"), 'role': fields.selection([('req-participant', 'Participation required'), \ ('chair', 'Chair Person'), \ ('opt-participant', 'Optional Participation'), \ ('non-participant', 'For information Purpose')], 'Role', \ help='Participation role for the calendar user'), 'state': fields.selection([('needs-action', 'Needs Action'), ('tentative', 'Uncertain'), ('declined', 'Declined'), ('accepted', 'Accepted'), ('delegated', 'Delegated')], 'Status', readonly=True, \ help="Status of the attendee's participation"), 'rsvp': fields.boolean('Required Reply?', help="Indicats whether the favor of a reply is requested"), 'delegated_to': fields.function(_compute_data, \ string='Delegated To', type="char", size=124, store=True, \ multi='delegated_to', help="The users that the original \ request was delegated to"), 'delegated_from': fields.function(_compute_data, string=\ 'Delegated From', type="char", store=True, size=124, multi='delegated_from'), 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \ 'attendee_id', 'parent_id', 'Delegrated From'), 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \ 'attendee_id', 'child_id', 'Delegrated To'), 'sent_by': fields.function(_compute_data, string='Sent By', \ type="char", multi='sent_by', store=True, size=124, \ help="Specify the user that is acting on behalf of the calendar user"), 'sent_by_uid': fields.function(_compute_data, string='Sent By User', \ type="many2one", relation="res.users", multi='sent_by_uid'), 'cn': fields.function(_compute_data, string='Common name', \ type="char", size=124, multi='cn', store=True), 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\ that points to the directory information corresponding to the attendee."), 'language': fields.function(_compute_data, string='Language', \ type="selection", selection=_lang_get, multi='language', \ store=True, help="To specify the language for text values in a\ property or property parameter."), 'user_id': fields.many2one('res.users', 'User'), 'partner_id': fields.many2one('res.partner', 'Contact'), 'email': fields.char('Email', size=124, help="Email of Invited Person"), 'event_date': fields.function(_compute_data, string='Event Date', \ type="datetime", multi='event_date'), 'event_end_date': fields.function(_compute_data, \ string='Event End Date', type="datetime", \ multi='event_end_date'), 'ref': fields.reference('Event Ref', selection=_links_get, size=128), 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), } _defaults = { 'state': 'needs-action', 'role': 'req-participant', 'rsvp': True, 'cutype': 'individual', } def copy(self, cr, uid, id, default=None, context=None): raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.')) def onchange_partner_id(self, cr, uid, ids, partner_id,context=None): """ Make entry on email and availbility on change of partner_id field. @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param partner_id: changed value of partner id @param context: a standard dictionary for contextual values @return: dictionary of values which put value in email and availability fields """ if not partner_id: return {'value': {'email': ''}} partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) return {'value': {'email': partner.email}} def get_ics_file(self, cr, uid, event_obj, context=None): """ Returns iCalendar file for the event invitation. @param self: the object pointer @param cr: the current row, from the database cursor @param uid: the current user's id for security checks @param event_obj: event object (browse record) @param context: a standard dictionary for contextual values @return: .ics file content """ res = None def ics_datetime(idate, short=False): if idate: #returns the datetime as UTC, because it is stored as it in the database return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.timezone('UTC')) return False try: # FIXME: why isn't this in CalDAV? import vobject except ImportError: return res cal = vobject.iCalendar() event = cal.add('vevent') if not event_obj.date_deadline or not event_obj.date: raise osv.except_osv(_('Warning!'),_("First you have to specify the date of the invitation.")) event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S')) event.add('dtstart').value = ics_datetime(event_obj.date) event.add('dtend').value = ics_datetime(event_obj.date_deadline) event.add('summary').value = event_obj.name if event_obj.description: event.add('description').value = event_obj.description if event_obj.location: event.add('location').value = event_obj.location if event_obj.rrule: event.add('rrule').value = event_obj.rrule if event_obj.organizer: event_org = event.add('organizer') event_org.params['CN'] = [event_obj.organizer] event_org.value = 'MAILTO:' + (event_obj.organizer) elif event_obj.user_id or event_obj.organizer_id: event_org = event.add('organizer') organizer = event_obj.organizer_id if not organizer: organizer = event_obj.user_id event_org.params['CN'] = [organizer.name] event_org.value = 'MAILTO:' + (organizer.email or organizer.name) if event_obj.alarm_id: # computes alarm data valarm = event.add('valarm') alarm_object = self.pool.get('res.alarm') alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context) # Compute trigger data interval = alarm_data['trigger_interval'] occurs = alarm_data['trigger_occurs'] duration = (occurs == 'after' and alarm_data['trigger_duration']) \ or -(alarm_data['trigger_duration']) related = alarm_data['trigger_related'] trigger = valarm.add('TRIGGER') trigger.params['related'] = [related.upper()] if interval == 'days': delta = timedelta(days=duration) if interval == 'hours': delta = timedelta(hours=duration) if interval == 'minutes': delta = timedelta(minutes=duration) trigger.value = delta # Compute other details valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP' for attendee in event_obj.attendee_ids: attendee_add = event.add('attendee') attendee_add.params['CUTYPE'] = [str(attendee.cutype)] attendee_add.params['ROLE'] = [str(attendee.role)] attendee_add.params['RSVP'] = [str(attendee.rsvp)] attendee_add.value = 'MAILTO:' + (attendee.email or '') res = cal.serialize() return res def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None): """ Send mail for event invitation to event attendees. @param email_from: email address for user sending the mail @return: True """ company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name for att in self.browse(cr, uid, ids, context=context): sign = att.sent_by_uid and att.sent_by_uid.signature or '' sign = '
'.join(sign and sign.split('\n') or []) res_obj = att.ref if res_obj: att_infos = [] sub = res_obj.name other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))]) for att2 in self.browse(cr, uid, other_invitation_ids): att_infos.append(((att2.user_id and att2.user_id.name) or \ (att2.partner_id and att2.partner_id.name) or \ att2.email) + ' - Status: ' + att2.state.title()) #dates and times are gonna be expressed in `tz` time (local timezone of the `uid`) tz = context.get('tz', pytz.timezone('UTC')) #res_obj.date and res_obj.date_deadline are in UTC in database so we use context_timestamp() to transform them in the `tz` timezone date_start = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) date_stop = False if res_obj.date_deadline: date_stop = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) body_vals = {'name': res_obj.name, 'start_date': date_start, 'end_date': date_stop, 'timezone': tz, 'description': res_obj.description or '-', 'location': res_obj.location or '-', 'attendees': '
'.join(att_infos), 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User', 'sign': sign, 'company': company } body = html_invitation % body_vals if mail_to and email_from: ics_file = self.get_ics_file(cr, uid, res_obj, context=context) vals = {'email_from': email_from, 'email_to': mail_to, 'state': 'outgoing', 'subject': sub, 'body_html': body, 'auto_delete': True} if ics_file: vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', 'datas_fname': 'invitation.ics', 'datas': str(ics_file).encode('base64')})] self.pool.get('mail.mail').create(cr, uid, vals, context=context) return True def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv): """ Make entry on email and availbility on change of user_id field. @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param user_id: changed value of User id @return: dictionary of values which put value in email and availability fields """ if not user_id: return {'value': {'email': ''}} usr_obj = self.pool.get('res.users') user = usr_obj.browse(cr, uid, user_id, *args) return {'value': {'email': user.email, 'availability':user.availability}} def do_tentative(self, cr, uid, ids, context=None, *args): """ Makes event invitation as Tentative. @param self: the object pointer @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param *args: get Tupple value @param context: a standard dictionary for contextual values """ return self.write(cr, uid, ids, {'state': 'tentative'}, context) def do_accept(self, cr, uid, ids, context=None, *args): """ Marks event invitation as Accepted. @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param context: a standard dictionary for contextual values @return: True """ if context is None: context = {} return self.write(cr, uid, ids, {'state': 'accepted'}, context) def do_decline(self, cr, uid, ids, context=None, *args): """ Marks event invitation as Declined. @param self: the object pointer @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param ids: list of calendar attendee's IDs @param *args: get Tupple value @param context: a standard dictionary for contextual values """ if context is None: context = {} return self.write(cr, uid, ids, {'state': 'declined'}, context) def create(self, cr, uid, vals, context=None): """ Overrides orm create method. @param self: The object pointer @param cr: the current row, from the database cursor @param uid: the current user's ID for security checks @param vals: get Values @param context: a standard dictionary for contextual values """ if context is None: context = {} if not vals.get("email") and vals.get("cn"): cnval = vals.get("cn").split(':') email = filter(lambda x:x.__contains__('@'), cnval) vals['email'] = email and email[0] or '' vals['cn'] = vals.get("cn") res = super(calendar_attendee, self).create(cr, uid, vals, context=context) return res class res_alarm(osv.osv): """Resource Alarm """ _name = 'res.alarm' _description = 'Basic Alarm Information' _columns = { 'name':fields.char('Name', size=256, required=True), 'trigger_occurs': fields.selection([('before', 'Before'), \ ('after', 'After')], \ 'Triggers', required=True), 'trigger_interval': fields.selection([('minutes', 'Minutes'), \ ('hours', 'Hours'), \ ('days', 'Days')], 'Interval', \ required=True), 'trigger_duration': fields.integer('Duration', required=True), 'trigger_related': fields.selection([('start', 'The event starts'), \ ('end', 'The event ends')], \ 'Related to', required=True), 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \ are both optional, but if one occurs, so MUST the other"""), 'repeat': fields.integer('Repeat'), '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.") } _defaults = { 'trigger_interval': 'minutes', 'trigger_duration': 5, 'trigger_occurs': 'before', 'trigger_related': 'start', 'active': 1, } def do_alarm_create(self, cr, uid, ids, model, date, context=None): """ Create Alarm for event. @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of res alarm's IDs. @param model: Model name. @param date: Event date @param context: A standard dictionary for contextual values @return: True """ if context is None: context = {} alarm_obj = self.pool.get('calendar.alarm') res_alarm_obj = self.pool.get('res.alarm') ir_obj = self.pool.get('ir.model') model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0] model_obj = self.pool[model] for data in model_obj.browse(cr, uid, ids, context=context): basic_alarm = data.alarm_id cal_alarm = data.base_calendar_alarm_id if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm): new_res_alarm = None # Find for existing res.alarm duration = cal_alarm.trigger_duration interval = cal_alarm.trigger_interval occurs = cal_alarm.trigger_occurs related = cal_alarm.trigger_related domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)] alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context) if not alarm_ids: val = { 'trigger_duration': duration, 'trigger_interval': interval, 'trigger_occurs': occurs, 'trigger_related': related, 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs) } new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context) else: new_res_alarm = alarm_ids[0] cr.execute('UPDATE %s ' % model_obj._table + \ ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \ ' WHERE id=%s', (cal_alarm.id, new_res_alarm, data.id)) self.do_alarm_unlink(cr, uid, [data.id], model) if basic_alarm: vals = { 'action': 'display', 'description': data.description, 'name': data.name, 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))], 'trigger_related': basic_alarm.trigger_related, 'trigger_duration': basic_alarm.trigger_duration, 'trigger_occurs': basic_alarm.trigger_occurs, 'trigger_interval': basic_alarm.trigger_interval, 'duration': basic_alarm.duration, 'repeat': basic_alarm.repeat, 'state': 'run', 'event_date': data[date], 'res_id': data.id, 'model_id': model_id, 'user_id': uid } alarm_id = alarm_obj.create(cr, uid, vals) cr.execute('UPDATE %s ' % model_obj._table + \ ' SET base_calendar_alarm_id=%s, alarm_id=%s ' ' WHERE id=%s', \ ( alarm_id, basic_alarm.id, data.id) ) return True def do_alarm_unlink(self, cr, uid, ids, model, context=None): """ Delete alarm specified in ids @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of res alarm's IDs. @param model: Model name for which alarm is to be cleared. @return: True """ if context is None: context = {} alarm_obj = self.pool.get('calendar.alarm') ir_obj = self.pool.get('ir.model') model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0] model_obj = self.pool[model] for data in model_obj.browse(cr, uid, ids, context=context): alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', data.id)]) if alarm_ids: alarm_obj.unlink(cr, uid, alarm_ids) cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\ where id=%%s' % model_obj._table,(data.id,)) return True class calendar_alarm(osv.osv): _name = 'calendar.alarm' _description = 'Event alarm information' _inherit = 'res.alarm' __attribute__ = {} _columns = { 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'), 'name': fields.char('Summary', size=124, help="""Contains the text to be \ used as the message subject for email \ or contains the text to be used for display"""), 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \ ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \ required=True, help="Defines the action to be invoked when an alarm is triggered"), 'description': fields.text('Description', help='Provides a more complete \ description of the calendar component, than that \ provided by the "SUMMARY" property'), 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \ 'alarm_id', 'attendee_id', 'Attendees', readonly=True), 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\ which is rendered when the alarm is triggered for audio, * File which is intended to be sent as message attachments for email, * Points to a procedure resource, which is invoked when\ the alarm is triggered for procedure."""), 'res_id': fields.integer('Resource ID'), 'model_id': fields.many2one('ir.model', 'Model'), 'user_id': fields.many2one('res.users', 'Owner'), 'event_date': fields.datetime('Event Date'), 'event_end_date': fields.datetime('Event End Date'), 'trigger_date': fields.datetime('Trigger Date', readonly="True"), 'state':fields.selection([ ('draft', 'Draft'), ('run', 'Run'), ('stop', 'Stop'), ('done', 'Done'), ], 'Status', select=True, readonly=True), } _defaults = { 'action': 'email', 'state': 'run', } def create(self, cr, uid, vals, context=None): """ Overrides orm create method. @param self: The object pointer @param cr: the current row, from the database cursor, @param vals: dictionary of fields value.{'name_of_the_field': value, ...} @param context: A standard dictionary for contextual values @return: new record id for calendar_alarm. """ if context is None: context = {} event_date = vals.get('event_date', False) if event_date: dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S") if vals['trigger_interval'] == 'days': delta = timedelta(days=vals['trigger_duration']) if vals['trigger_interval'] == 'hours': delta = timedelta(hours=vals['trigger_duration']) if vals['trigger_interval'] == 'minutes': delta = timedelta(minutes=vals['trigger_duration']) trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta) vals['trigger_date'] = trigger_date res = super(calendar_alarm, self).create(cr, uid, vals, context=context) return res def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \ context=None): """Scheduler for event reminder @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of calendar alarm's IDs. @param use_new_cursor: False or the dbname @param context: A standard dictionary for contextual values """ if context is None: context = {} current_datetime = datetime.now() alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context) mail_to = "" for alarm in self.browse(cr, uid, alarm_ids, context=context): next_trigger_date = None update_vals = {} model_obj = self.pool[alarm.model_id.model] res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context) re_dates = [] if hasattr(res_obj, 'rrule') and res_obj.rrule: event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S') #exdate is a string and we need a list exdate = res_obj.exdate and res_obj.exdate.split(',') or [] recurrent_dates = get_recurrent_dates(res_obj.rrule, exdate, event_date, res_obj.exrule) trigger_interval = alarm.trigger_interval if trigger_interval == 'days': delta = timedelta(days=alarm.trigger_duration) if trigger_interval == 'hours': delta = timedelta(hours=alarm.trigger_duration) if trigger_interval == 'minutes': delta = timedelta(minutes=alarm.trigger_duration) delta = alarm.trigger_occurs == 'after' and delta or -delta for rdate in recurrent_dates: if rdate + delta > current_datetime: break if rdate + delta <= current_datetime: re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S")) rest_dates = recurrent_dates[len(re_dates):] next_trigger_date = rest_dates and rest_dates[0] or None else: re_dates = [alarm.trigger_date] if re_dates: if alarm.action == 'email': sub = '[OpenERP Reminder] %s' % (alarm.name) body = """
Event: %s
Event Date: %s
Description: %s

From:
      %s

----
%s
""" % (alarm.name, alarm.trigger_date, alarm.description, \ alarm.user_id.name, alarm.user_id.signature) mail_to = alarm.user_id.email for att in alarm.attendee_ids: mail_to = mail_to + " " + att.user_id.email if mail_to: vals = { 'state': 'outgoing', 'subject': sub, 'body_html': body, 'email_to': mail_to, 'email_from': tools.config.get('email_from', mail_to), } self.pool.get('mail.mail').create(cr, uid, vals, context=context) if next_trigger_date: update_vals.update({'trigger_date': next_trigger_date}) else: update_vals.update({'state': 'done'}) self.write(cr, uid, [alarm.id], update_vals) return True class calendar_event(osv.osv): _name = "calendar.event" _description = "Calendar Event" __attribute__ = {} def _tz_get(self, cr, uid, context=None): return [(x.lower(), x) for x in pytz.all_timezones] def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None): """Returns duration and/or end date based on values passed @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of calendar event's IDs. @param start_date: Starting date @param duration: Duration between start date and end date @param end_date: Ending Datee @param context: A standard dictionary for contextual values """ if context is None: context = {} value = {} if not start_date: return value if not end_date and not duration: duration = 1.00 value['duration'] = duration start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S") if allday: # For all day event duration = 24.0 value['duration'] = duration # change start_date's time to 00:00:00 in the user's timezone user = self.pool.get('res.users').browse(cr, uid, uid) tz = pytz.timezone(user.tz) if user.tz else pytz.utc start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone start = start.replace(hour=0, minute=0, second=0) # change start's time to 00:00:00 start = start.astimezone(pytz.utc) # convert start back to utc start_date = start.strftime("%Y-%m-%d %H:%M:%S") value['date'] = start_date if end_date and not duration: end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") diff = end - start duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) value['duration'] = round(duration, 2) elif not end_date: end = start + timedelta(hours=duration) value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S") elif end_date and duration and not allday: # we have both, keep them synchronized: # set duration based on end_date (arbitrary decision: this avoid # getting dates like 06:31:48 instead of 06:32:00) end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") diff = end - start duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) value['duration'] = round(duration, 2) return {'value': value} def unlink_events(self, cr, uid, ids, context=None): """ This function deletes event which are linked with the event with recurrent_id (Removes the events which refers to the same UID value) """ if context is None: context = {} for event_id in ids: cr.execute("select id from %s where recurrent_id=%%s" % (self._table), (event_id,)) r_ids = map(lambda x: x[0], cr.fetchall()) self.unlink(cr, uid, r_ids, context=context) return True def _get_rulestring(self, cr, uid, ids, name, arg, context=None): """ Gets Recurrence rule string according to value type RECUR of iCalendar from the values given. @param self: The object pointer @param cr: the current row, from the database cursor, @param id: List of calendar event's ids. @param context: A standard dictionary for contextual values @return: dictionary of rrule value. """ result = {} if not isinstance(ids, list): ids = [ids] for id in ids: #read these fields as SUPERUSER because if the record is private a normal search could return False and raise an error data = self.read(cr, SUPERUSER_ID, id, ['interval', 'count'], context=context) if data.get('interval', 0) < 0: raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.')) if data.get('count', 0) <= 0: raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.')) data = self.read(cr, uid, id, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'select1', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'exrule', 'day', 'week_list' ], context=context) event = data['id'] if data['recurrency']: result[event] = self.compute_rule_string(data) else: result[event] = "" return result def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None): data = self._get_empty_rrule_data() if field_value: data['recurrency'] = True for event in self.browse(cr, uid, ids, context=context): rdate = rule_date or event.date update_data = self._parse_rrule(field_value, dict(data), rdate) data.update(update_data) super(calendar_event, obj).write(cr, uid, ids, data, context=context) return True _columns = { 'id': fields.integer('ID', readonly=True), 'sequence': fields.integer('Sequence'), 'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}), 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,), 'date_deadline': fields.datetime('End Date', states={'done': [('readonly', True)]}, required=True,), 'create_date': fields.datetime('Created', readonly=True), 'duration': fields.float('Duration', states={'done': [('readonly', True)]}), 'description': fields.text('Description', states={'done': [('readonly', True)]}), 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \ ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}), 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}), 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \ 'Show Time as', states={'done': [('readonly', True)]}), 'base_calendar_url': fields.char('Caldav URL', size=264), 'state': fields.selection([ ('tentative', 'Uncertain'), ('cancelled', 'Cancelled'), ('confirmed', 'Confirmed'), ], 'Status', readonly=True), 'exdate': fields.text('Exception Date/Times', help="This property \ defines the list of date/time exceptions for a recurring calendar component."), 'exrule': fields.char('Exception Rule', size=352, help="Defines a \ rule or repeating pattern of time to exclude from the recurring rule."), 'rrule': fields.function(_get_rulestring, type='char', size=124, \ fnct_inv=_rrule_write, store=True, string='Recurrent Rule'), '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"), 'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]}, help="Set an alarm at this time, before the event occurs" ), 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'), 'recurrent_id': fields.integer('Recurrent ID'), 'recurrent_id_date': fields.datetime('Recurrent ID date'), 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'), 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}), 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with organizer attribute of VEvent. 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}), 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'), 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"), 'count': fields.integer('Repeat', help="Repeat x times"), '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'), 'select1': fields.selection([('date', 'Date of month'), ('day', 'Day of month')], 'Option'), 'day': fields.integer('Date of month'), 'week_list': fields.selection([ ('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'), ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'), ('SU', 'Sunday')], 'Weekday'), 'byday': fields.selection([ ('1', 'First'), ('2', 'Second'), ('3', 'Third'), ('4', 'Fourth'), ('5', 'Fifth'), ('-1', 'Last')], 'By day'), 'month_list': fields.selection(months.items(), 'Month'), 'end_date': fields.date('Repeat Until'), 'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \ 'event_id', 'attendee_id', 'Attendees'), 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}), '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."), 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), 'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}), } def create_attendees(self, cr, uid, ids, context): att_obj = self.pool.get('calendar.attendee') user_obj = self.pool.get('res.users') current_user = user_obj.browse(cr, uid, uid, context=context) for event in self.browse(cr, uid, ids, context): attendees = {} for att in event.attendee_ids: attendees[att.partner_id.id] = True new_attendees = [] mail_to = "" for partner in event.partner_ids: if partner.id in attendees: continue local_context = context.copy() local_context.pop('default_state', None) att_id = self.pool.get('calendar.attendee').create(cr, uid, { 'partner_id': partner.id, 'user_id': partner.user_ids and partner.user_ids[0].id or False, 'ref': self._name+','+str(event.id), 'email': partner.email }, context=local_context) if partner.email: mail_to = mail_to + " " + partner.email self.write(cr, uid, [event.id], { 'attendee_ids': [(4, att_id)] }, context=context) new_attendees.append(att_id) if mail_to and current_user.email: att_obj._send_mail(cr, uid, new_attendees, mail_to, email_from = current_user.email, context=context) return True def default_organizer(self, cr, uid, context=None): user_pool = self.pool.get('res.users') user = user_pool.browse(cr, uid, uid, context=context) res = user.name if user.email: res += " <%s>" %(user.email) return res _defaults = { 'end_type': 'count', 'count': 1, 'rrule_type': False, 'state': 'tentative', 'class': 'public', 'show_as': 'busy', 'select1': 'date', 'interval': 1, 'active': 1, 'user_id': lambda self, cr, uid, ctx: uid, 'organizer': default_organizer, } def _check_closing_date(self, cr, uid, ids, context=None): for event in self.browse(cr, uid, ids, context=context): if event.date_deadline < event.date: return False return True _constraints = [ (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']), ] def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None): """Gives virtual event ids for recurring events based on value of Recurrence Rule This method gives ids of dates that comes between start date and end date of calendar views @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param limit: The Number of Results to Return """ if not context: context = {} result = [] for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'recurrency', 'exdate', 'exrule', 'date'], context=context): if not data['recurrency'] or not data['rrule']: result.append(data['id']) continue event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S") # TOCHECK: the start date should be replaced by event date; the event date will be changed by that of calendar code if not data['rrule']: continue exdate = data['exdate'] and data['exdate'].split(',') or [] rrule_str = data['rrule'] new_rrule_str = [] rrule_until_date = False is_until = False for rule in rrule_str.split(';'): name, value = rule.split('=') if name == "UNTIL": is_until = True value = parser.parse(value) rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S")) value = value.strftime("%Y%m%d%H%M%S") new_rule = '%s=%s' % (name, value) new_rrule_str.append(new_rule) new_rrule_str = ';'.join(new_rrule_str) rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule']) 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 = [] for arg in domain: if str(arg[0]) in (str('date'), str('date_deadline')): 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')='): 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 idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S")) result.append(idval) if isinstance(select, (str, int, long)): return ids and ids[0] or False else: ids = list(set(result)) return ids def compute_rule_string(self, data): """ Compute rule string according to value type RECUR of iCalendar from the values given. @param self: the object pointer @param data: dictionary of freq and interval value @return: string containing recurring rule (empty if no rule) """ def get_week_string(freq, data): weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] if freq == 'weekly': byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data)) if byday: return ';BYDAY=' + ','.join(byday) return '' def get_month_string(freq, data): if freq == 'monthly': if data.get('select1')=='date' and (data.get('day') < 1 or data.get('day') > 31): raise osv.except_osv(_('Error!'), ("Please select a proper day of the month.")) if data.get('select1')=='day': return ';BYDAY=' + data.get('byday') + data.get('week_list') elif data.get('select1')=='date': return ';BYMONTHDAY=' + str(data.get('day')) return '' def get_end_date(data): if data.get('end_date'): data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z' return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\ ((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '') freq = data.get('rrule_type', False) res = '' if freq: interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or '' res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data) return res def _get_empty_rrule_data(self): return { 'byday' : False, 'recurrency' : False, 'end_date' : False, 'rrule_type' : False, 'select1' : False, 'interval' : 0, 'count' : False, 'end_type' : False, 'mo' : False, 'tu' : False, 'we' : False, 'th' : False, 'fr' : False, 'sa' : False, 'su' : False, 'exrule' : False, 'day' : False, 'week_list' : False } def _parse_rrule(self, rule, data, date_start): day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] rrule_type = ['yearly', 'monthly', 'weekly', 'daily'] r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S")) if r._freq > 0 and r._freq < 4: data['rrule_type'] = rrule_type[r._freq] data['count'] = r._count data['interval'] = r._interval data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S") #repeat weekly if r._byweekday: for i in xrange(0,7): if i in r._byweekday: data[day_list[i]] = True data['rrule_type'] = 'weekly' #repeat monthly by nweekday ((weekday, weeknumber), ) if r._bynweekday: data['week_list'] = day_list[r._bynweekday[0][0]].upper() data['byday'] = r._bynweekday[0][1] data['select1'] = 'day' data['rrule_type'] = 'monthly' if r._bymonthday: data['day'] = r._bymonthday[0] data['select1'] = 'date' data['rrule_type'] = 'monthly' #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times if r._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('end_date')): data['count'] = 100 if data.get('count'): data['end_type'] = 'count' else: data['end_type'] = 'end_date' return data def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): if context is None: context = {} new_args = [] for arg in args: new_arg = arg if arg[0] in ('date_deadline', unicode('date_deadline')): if context.get('virtual_id', True): new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] elif arg[0] == "id": new_id = get_real_ids(arg[2]) new_arg = (arg[0], arg[1], new_id) new_args.append(new_arg) #offset, limit and count must be treated separately as we may need to deal with virtual ids res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False) if context.get('virtual_id', True): res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context) if count: return len(res) elif limit: return res[offset:offset+limit] return res def _get_data(self, cr, uid, id, context=None): return self.read(cr, uid, id,['date', 'date_deadline']) def need_to_update(self, event_id, vals): split_id = str(event_id).split("-") if len(split_id) < 2: return False else: date_start = vals.get('date', '') try: date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S") return date_start == split_id[1] except Exception: return True def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True): def _only_changes_to_apply_on_real_ids(field_names): ''' return True if changes are only to be made on the real ids''' for field in field_names: if field not in ['message_follower_ids']: return False return True context = context or {} if isinstance(ids, (str, int, long)): ids = [ids] res = False # Special write of complex IDS for event_id in ids[:]: if len(str(event_id).split('-')) == 1: continue ids.remove(event_id) real_event_id = base_calendar_id2real_id(event_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. if not vals.get('recurrency', True) or _only_changes_to_apply_on_real_ids(vals.keys()): ids.append(real_event_id) continue #if edit one instance of a reccurrent id data = self.read(cr, uid, event_id, ['date', 'date_deadline', \ 'rrule', 'duration', 'exdate']) if data.get('rrule'): data.update( vals, recurrent_id=real_event_id, recurrent_id_date=data.get('date'), rrule_type=False, rrule='', recurrency=False, ) #do not copy the id if data.get('id'): del(data['id']) new_id = self.copy(cr, uid, real_event_id, default=data, context=context) date_new = event_id.split('-')[1] date_new = time.strftime("%Y%m%dT%H%M%S", \ time.strptime(date_new, "%Y%m%d%H%M%S")) exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new res = self.write(cr, uid, [real_event_id], {'exdate': exdate}) context.update({'active_id': new_id, 'active_ids': [new_id]}) continue if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'): vals['vtimezone'] = vals['vtimezone'][40:] res = super(calendar_event, self).write(cr, uid, ids, vals, context=context) # set end_date for calendar searching if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \ (vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')): for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): end_date = self._set_recurrency_end_date(data, context=context) super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) if vals.get('partner_ids', False): self.create_attendees(cr, uid, ids, context) if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\ or ('date' in vals or 'duration' in vals or 'date_deadline' in vals): alarm_obj = self.pool.get('res.alarm') alarm_obj.do_alarm_create(cr, uid, ids, self._name, 'date', context=context) return res or True and False def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): if not context: context = {} if 'date' in groupby: raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.')) virtual_id = context.get('virtual_id', True) context.update({'virtual_id': False}) res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) for re in res: #remove the count, since the value is not consistent with the result of the search when expand the group for groupname in groupby: if re.get(groupname + "_count"): del re[groupname + "_count"] re.get('__context', {}).update({'virtual_id' : virtual_id}) return res def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): if context is None: context = {} fields2 = fields and fields[:] or None EXTRAFIELDS = ('class','user_id','duration') for f in EXTRAFIELDS: if fields and (f not in fields): fields2.append(f) # FIXME This whole id mangling has to go! if isinstance(ids, (str, int, long)): select = [ids] else: select = ids select = map(lambda x: (x, base_calendar_id2real_id(x)), select) result = [] real_data = super(calendar_event, self).read(cr, uid, [real_id for base_calendar_id, real_id in select], fields=fields2, context=context, load=load) real_data = dict(zip([x['id'] for x in real_data], real_data)) for base_calendar_id, real_id in select: res = real_data[real_id].copy() ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0) if not isinstance(ls, (str, int, long)) and len(ls) >= 2: res['date'] = ls[1] res['date_deadline'] = ls[2] res['id'] = base_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==uid: continue if r['class']=='private': for f in r.keys(): if f not in ('id','date','date_deadline','duration','user_id','state','interval','count'): 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] if isinstance(ids, (str, int, long)): return result and result[0] or False return result def copy(self, cr, uid, id, default=None, context=None): if context is None: context = {} res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context) alarm_obj = self.pool.get('res.alarm') alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context) return res def unlink(self, cr, uid, ids, context=None): if not isinstance(ids, list): ids = [ids] res = False attendee_obj=self.pool.get('calendar.attendee') for event_id in ids[:]: if len(str(event_id).split('-')) == 1: continue real_event_id = base_calendar_id2real_id(event_id) data = self.read(cr, uid, real_event_id, ['exdate'], context=context) date_new = event_id.split('-')[1] date_new = time.strftime("%Y%m%dT%H%M%S", \ time.strptime(date_new, "%Y%m%d%H%M%S")) exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new self.write(cr, uid, [real_event_id], {'exdate': exdate}) ids.remove(event_id) for event in self.browse(cr, uid, ids, context=context): if event.attendee_ids: attendee_obj.unlink(cr, uid, [x.id for x in event.attendee_ids], context=context) res = super(calendar_event, self).unlink(cr, uid, ids, context=context) self.pool.get('res.alarm').do_alarm_unlink(cr, uid, ids, self._name) self.unlink_events(cr, uid, ids, context=context) return res def _set_recurrency_end_date(self, data, context=None): end_date = data.get('end_date') if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')): data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S') if data.get('rrule_type') in ('daily', unicode('count')): rel_date = relativedelta(days=data.get('count')+1) elif data.get('rrule_type') in ('weekly', unicode('weekly')): rel_date = relativedelta(days=(data.get('count')+1)*7) elif data.get('rrule_type') in ('monthly', unicode('monthly')): rel_date = relativedelta(months=data.get('count')+1) elif data.get('rrule_type') in ('yearly', unicode('yearly')): rel_date = relativedelta(years=data.get('count')+1) end_date = data_date_deadline + rel_date return end_date def create(self, cr, uid, vals, context=None): if context is None: context = {} if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'): vals['vtimezone'] = vals['vtimezone'][40:] vals['end_date'] = self._set_recurrency_end_date(vals, context=context) res = super(calendar_event, self).create(cr, uid, vals, context) alarm_obj = self.pool.get('res.alarm') alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context) self.create_attendees(cr, uid, [res], context) return res def do_tentative(self, cr, uid, ids, context=None, *args): """ Makes event invitation as Tentative @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of Event IDs @param *args: Get Tupple value @param context: A standard dictionary for contextual values """ return self.write(cr, uid, ids, {'state': 'tentative'}, context) def do_cancel(self, cr, uid, ids, context=None, *args): """ Makes event invitation as Tentative @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of Event IDs @param *args: Get Tupple value @param context: A standard dictionary for contextual values """ return self.write(cr, uid, ids, {'state': 'cancelled'}, context) def do_confirm(self, cr, uid, ids, context=None, *args): """ Makes event invitation as Tentative @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of Event IDs @param *args: Get Tupple value @param context: A standard dictionary for contextual values """ return self.write(cr, uid, ids, {'state': 'confirmed'}, context) class calendar_todo(osv.osv): """ Calendar Task """ _name = "calendar.todo" _inherit = "calendar.event" _description = "Calendar Task" def _get_date(self, cr, uid, ids, name, arg, context=None): """ Get Date @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of calendar todo's IDs. @param args: list of tuples of form [(‘name_of_the_field', ‘operator', value), ...]. @param context: A standard dictionary for contextual values """ res = {} for event in self.browse(cr, uid, ids, context=context): res[event.id] = event.date_start return res def _set_date(self, cr, uid, id, name, value, arg, context=None): """ Set Date @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param id: calendar's ID. @param value: Get Value @param args: list of tuples of form [('name_of_the_field', 'operator', value), ...]. @param context: A standard dictionary for contextual values """ assert name == 'date' return self.write(cr, uid, id, { 'date_start': value }, context=context) _columns = { 'date': fields.function(_get_date, fnct_inv=_set_date, \ string='Duration', store=True, type='datetime'), 'duration': fields.integer('Duration'), } __attribute__ = {} class ir_values(osv.osv): _inherit = 'ir.values' def set(self, cr, uid, key, key2, name, models, value, replace=True, \ isobject=False, meta=False, preserve_user=False, company=False): """ Set IR Values @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param model: Get The Model """ new_model = [] for data in models: if type(data) in (list, tuple): new_model.append((data[0], base_calendar_id2real_id(data[1]))) else: new_model.append(data) return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \ value, replace, isobject, meta, preserve_user, company) def get(self, cr, uid, key, key2, models, meta=False, context=None, \ res_id_req=False, without_user=True, key2_req=True): """ Get IR Values @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param model: Get The Model """ if context is None: context = {} new_model = [] for data in models: if type(data) in (list, tuple): new_model.append((data[0], base_calendar_id2real_id(data[1]))) else: new_model.append(data) return super(ir_values, self).get(cr, uid, key, key2, new_model, \ meta, context, res_id_req, without_user, key2_req) class ir_model(osv.osv): _inherit = 'ir.model' def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): """ Overrides orm read method. @param self: The object pointer @param cr: the current row, from the database cursor, @param uid: the current user's ID for security checks, @param ids: List of IR Model's IDs. @param context: A standard dictionary for contextual values """ new_ids = isinstance(ids, (str, int, long)) and [ids] or ids if context is None: context = {} data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \ context=context, load=load) if data: for val in data: val['id'] = base_calendar_id2real_id(val['id']) return isinstance(ids, (str, int, long)) and data[0] or data original_exp_report = openerp.service.report.exp_report def exp_report(db, uid, object, ids, data=None, context=None): """ Export Report @param db: get the current database, @param uid: the current user's ID for security checks, @param context: A standard dictionary for contextual values """ if object == 'printscreen.list': original_exp_report(db, uid, object, ids, data, context) new_ids = [] for id in ids: new_ids.append(base_calendar_id2real_id(id)) if data.get('id', False): data['id'] = base_calendar_id2real_id(data['id']) return original_exp_report(db, uid, object, new_ids, data, context) openerp.service.report.exp_report = exp_report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: