6067861d66a3a851e76d9cbce0f30760caa5bb23
[odoo/odoo.git] / addons / base_calendar / base_calendar.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from datetime import datetime, timedelta, date
23 from dateutil import parser
24 from dateutil import rrule
25 from dateutil.relativedelta import relativedelta
26 from osv import fields, osv
27 from service import web_services
28 from tools.translate import _
29 import pytz
30 import re
31 import time
32 import tools
33
34 months = {
35     1: "January", 2: "February", 3: "March", 4: "April", \
36     5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
37     10: "October", 11: "November", 12: "December"
38 }
39
40 def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
41     """
42     Get recurrent dates based on Rule string considering exdate and start date
43     @param rrulestring: Rulestring
44     @param exdate: List of exception dates for rrule
45     @param startdate: Startdate for computing recurrent dates
46     @return: List of Recurrent dates
47     """
48     def todate(date):
49         val = parser.parse(''.join((re.compile('\d')).findall(date)))
50         return val
51     
52     limit = 100
53
54     if not startdate:
55         startdate = datetime.now()
56         
57     if not exdate:
58         exdate = []
59     rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
60     i  = 0
61     for date in exdate:
62         i += 1
63         datetime_obj = todate(date)
64         rset1._exdate.append(datetime_obj)
65     
66     if exrule:
67         rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
68
69     rset1 = rset1[:limit]
70     #print rset1.count()
71     l =  rset1
72     print "len de liste" ,len(l)
73     return l
74
75 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
76     """
77     This function converts virtual event id into real id of actual event
78     @param base_calendar_id: Id of calendar
79     @param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
80     """
81
82     if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
83         res = base_calendar_id.split('-')
84
85         if len(res) >= 2:
86             real_id = res[0]
87             if with_date:
88                 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
89                                  time.strptime(res[1], "%Y%m%d%H%M%S"))
90                 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
91                 end = start + timedelta(hours=with_date)
92                 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
93             return int(real_id)
94
95     return base_calendar_id and int(base_calendar_id) or base_calendar_id
96
97 def real_id2base_calendar_id(real_id, recurrent_date):
98     """
99     Convert  real id of record into virtual id using recurrent_date
100     e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
101         1-20091201100000
102     @return: real id with recurrent date.
103     """
104
105     if real_id and recurrent_date:
106         recurrent_date = time.strftime("%Y%m%d%H%M%S", \
107                             time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
108         return '%d-%s' % (real_id, recurrent_date)
109     return real_id
110
111 def _links_get(self, cr, uid, context=None):
112     """
113     Get request link.
114     @param cr: the current row, from the database cursor,
115     @param uid: the current user’s ID for security checks,
116     @param context: A standard dictionary for contextual values
117     @return: list of dictionary which contain object and name and id.
118     """
119     obj = self.pool.get('res.request.link')
120     ids = obj.search(cr, uid, [])
121     res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
122     return [(r['object'], r['name']) for r in res]
123
124 html_invitation = """
125 <html>
126 <head>
127 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
128 <title>%(name)s</title>
129 </head>
130 <body>
131 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
132     style="font-family: Arial, Sans-serif; font-size: 14">
133     <tr>
134         <td width="100%%">Hello,</td>
135     </tr>
136     <tr>
137         <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
138     </tr>
139     <tr>
140         <td width="100%%">Below are the details of event:</td>
141     </tr>
142 </table>
143
144 <table cellspacing="0" cellpadding="5" border="0" summary=""
145     style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
146     <tr valign="center" align="center">
147         <td bgcolor="DFDFDF">
148         <h3>%(name)s</h3>
149         </td>
150     </tr>
151     <tr>
152         <td>
153         <table cellpadding="8" cellspacing="0" border="0"
154             style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
155             width="90%%">
156             <tr>
157                 <td width="21%%">
158                 <div><b>Start Date</b></div>
159                 </td>
160                 <td><b>:</b></td>
161                 <td>%(start_date)s</td>
162                 <td width="15%%">
163                 <div><b>End Date</b></div>
164                 </td>
165                 <td><b>:</b></td>
166                 <td width="25%%">%(end_date)s</td>
167             </tr>
168             <tr valign="top">
169                 <td><b>Description</b></td>
170                 <td><b>:</b></td>
171                 <td colspan="3">%(description)s</td>
172             </tr>
173             <tr valign="top">
174                 <td>
175                 <div><b>Location</b></div>
176                 </td>
177                 <td><b>:</b></td>
178                 <td colspan="3">%(location)s</td>
179             </tr>
180             <tr valign="top">
181                 <td>
182                 <div><b>Event Attendees</b></div>
183                 </td>
184                 <td><b>:</b></td>
185                 <td colspan="3">
186                 <div>
187                 <div>%(attendees)s</div>
188                 </div>
189                 </td>
190             </tr>
191         </table>
192         </td>
193     </tr>
194 </table>
195 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
196     style="font-family: Arial, Sans-serif; font-size: 14">
197     <tr>
198         <td width="100%%">From:</td>
199     </tr>
200     <tr>
201         <td width="100%%">%(user)s</td>
202     </tr>
203     <tr valign="top">
204         <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
205     </tr>
206     <tr>
207         <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
208     </tr>
209 </table>
210 </body>
211 </html>
212 """
213
214 class calendar_attendee(osv.osv):
215     """
216     Calendar Attendee Information
217     """
218     _name = 'calendar.attendee'
219     _description = 'Attendee information'
220     _rec_name = 'cutype'
221
222     __attribute__ = {}
223
224     def _get_address(self, name=None, email=None):
225         """
226         Gives email information in ical CAL-ADDRESS type format
227         @param name: Name for CAL-ADDRESS value
228         @param email: Email address for CAL-ADDRESS value
229         """
230         if name and email:
231             name += ':'
232         return (name or '') + (email and ('MAILTO:' + email) or '')
233
234     def _compute_data(self, cr, uid, ids, name, arg, context=None):
235         """
236         Compute data on function fields for attendee values .
237         @param cr: the current row, from the database cursor,
238         @param uid: the current user’s ID for security checks,
239         @param ids: List of calendar attendee’s IDs.
240         @param name: name of field.
241         @param context: A standard dictionary for contextual values
242         @return: Dictionary of form {id: {'field Name': value'}}.
243         """
244         name = name[0]
245         result = {}
246         for attdata in self.browse(cr, uid, ids, context=context):
247             id = attdata.id
248             result[id] = {}
249             if name == 'sent_by':
250                 if not attdata.sent_by_uid:
251                     result[id][name] = ''
252                     continue
253                 else:
254                     result[id][name] = self._get_address(attdata.sent_by_uid.name, \
255                                         attdata.sent_by_uid.address_id.email)
256
257             if name == 'cn':
258                 if attdata.user_id:
259                     result[id][name] = attdata.user_id.name
260                 elif attdata.partner_address_id:
261                     result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
262                 else:
263                     result[id][name] = attdata.email or ''
264
265             if name == 'delegated_to':
266                 todata = []
267                 for child in attdata.child_ids:
268                     if child.email:
269                         todata.append('MAILTO:' + child.email)
270                 result[id][name] = ', '.join(todata)
271
272             if name == 'delegated_from':
273                 fromdata = []
274                 for parent in attdata.parent_ids:
275                     if parent.email:
276                         fromdata.append('MAILTO:' + parent.email)
277                 result[id][name] = ', '.join(fromdata)
278
279             if name == 'event_date':
280                 if attdata.ref:
281                     result[id][name] = attdata.ref.date
282                 else:
283                     result[id][name] = False
284
285             if name == 'event_end_date':
286                 if attdata.ref:
287                     result[id][name] = attdata.ref.date_deadline
288                 else:
289                     result[id][name] = False
290
291             if name == 'sent_by_uid':
292                 if attdata.ref:
293                     result[id][name] = (attdata.ref.user_id.id, attdata.ref.user_id.name)
294                 else:
295                     result[id][name] = uid
296
297             if name == 'language':
298                 user_obj = self.pool.get('res.users')
299                 lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
300                 result[id][name] = lang.replace('_', '-')
301
302         return result
303
304     def _links_get(self, cr, uid, context=None):
305         """
306         Get request link for ref field in calendar attendee.
307         @param cr: the current row, from the database cursor,
308         @param uid: the current user’s ID for security checks,
309         @param context: A standard dictionary for contextual values
310         @return: list of dictionary which contain object and name and id.
311         """
312         obj = self.pool.get('res.request.link')
313         ids = obj.search(cr, uid, [])
314         res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
315         return [(r['object'], r['name']) for r in res]
316
317     def _lang_get(self, cr, uid, context=None):
318         """
319         Get language for language selection field.
320         @param cr: the current row, from the database cursor,
321         @param uid: the current user’s ID for security checks,
322         @param context: A standard dictionary for contextual values
323         @return: list of dictionary which contain code and name and id.
324         """
325         obj = self.pool.get('res.lang')
326         ids = obj.search(cr, uid, [])
327         res = obj.read(cr, uid, ids, ['code', 'name'], context=context)
328         res = [((r['code']).replace('_', '-').lower(), r['name']) for r in res]
329         return res
330
331     _columns = {
332         'cutype': fields.selection([('individual', 'Individual'), \
333                     ('group', 'Group'), ('resource', 'Resource'), \
334                     ('room', 'Room'), ('unknown', '') ], \
335                     'Invite Type', help="Specify the type of Invitation"),
336         'member': fields.char('Member', size=124,
337                     help="Indicate the groups that the attendee belongs to"),
338         'role': fields.selection([('req-participant', 'Participation required'), \
339                     ('chair', 'Chair Person'), \
340                     ('opt-participant', 'Optional Participation'), \
341                     ('non-participant', 'For information Purpose')], 'Role', \
342                     help='Participation role for the calendar user'),
343         'state': fields.selection([('tentative', 'Tentative'),
344                         ('needs-action', 'Needs Action'),
345                         ('accepted', 'Accepted'),
346                         ('declined', 'Declined'),
347                         ('delegated', 'Delegated')], 'State', readonly=True, \
348                         help="Status of the attendee's participation"),
349         'rsvp':  fields.boolean('Required Reply?',
350                     help="Indicats whether the favor of a reply is requested"),
351         'delegated_to': fields.function(_compute_data, method=True, \
352                 string='Delegated To', type="char", size=124, store=True, \
353                 multi='delegated_to', help="The users that the original \
354 request was delegated to"),
355         'delegated_from': fields.function(_compute_data, method=True, string=\
356             'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
357         'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
358                                     'attendee_id', 'parent_id', 'Delegrated From'),
359         'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
360                                       'attendee_id', 'child_id', 'Delegrated To'),
361         'sent_by': fields.function(_compute_data, method=True, string='Sent By', \
362                         type="char", multi='sent_by', store=True, size=124, \
363                         help="Specify the user that is acting on behalf of the calendar user"),
364         'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', \
365                             type="many2one", relation="res.users", multi='sent_by_uid'),
366         'cn': fields.function(_compute_data, method=True, string='Common name', \
367                             type="char", size=124, multi='cn', store=True),
368         'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
369 that points to the directory information corresponding to the attendee."),
370         'language': fields.function(_compute_data, method=True, string='Language', \
371                     type="selection", selection=_lang_get, multi='language', \
372                     store=True, help="To specify the language for text values in a\
373 property or property parameter."),
374         'user_id': fields.many2one('res.users', 'User'),
375         'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
376         'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', \
377                         relation='res.partner', string='Partner', help="Partner related to contact"),
378         'email': fields.char('Email', size=124, help="Email of Invited Person"),
379         'event_date': fields.function(_compute_data, method=True, string='Event Date', \
380                             type="datetime", multi='event_date'),
381         'event_end_date': fields.function(_compute_data, method=True, \
382                             string='Event End Date', type="datetime", \
383                             multi='event_end_date'),
384         'ref': fields.reference('Event Ref', selection=_links_get, size=128),
385         'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
386     }
387
388     _defaults = {
389         'state': 'needs-action',
390         'role': 'req-participant',
391         'rsvp':  True,
392         'cutype': 'individual',
393     }
394
395     def copy(self, cr, uid, id, default=None, context=None):
396         raise osv.except_osv(_('Warning!'), _('Can not Duplicate'))
397
398     def get_ics_file(self, cr, uid, event_obj, context=None):
399         """
400         Returns iCalendar file for the event invitation
401         @param self: The object pointer
402         @param cr: the current row, from the database cursor,
403         @param uid: the current user’s ID for security checks,
404         @param event_obj: Event object (browse record)
405         @param context: A standard dictionary for contextual values
406         @return: .ics file content
407         """
408         res = None
409         def ics_datetime(idate, short=False):
410             if idate:
411                 if short or len(idate)<=10:
412                     return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
413                 else:
414                     return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
415             else:
416                 return False
417         try:
418             # FIXME: why isn't this in CalDAV?
419             import vobject
420         except ImportError:
421             return res
422         cal = vobject.iCalendar()
423         event = cal.add('vevent')
424         if not event_obj.date_deadline or not event_obj.date:
425               raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
426         event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
427         event.add('dtstart').value = ics_datetime(event_obj.date)
428         event.add('dtend').value = ics_datetime(event_obj.date_deadline)
429         event.add('summary').value = event_obj.name
430         if  event_obj.description:
431             event.add('description').value = event_obj.description
432         if event_obj.location:
433             event.add('location').value = event_obj.location
434         if event_obj.rrule:
435             event.add('rrule').value = event_obj.rrule
436         if event_obj.organizer:
437             event_org = event.add('organizer')
438             event_org.params['CN'] = [event_obj.organizer]
439             event_org.value = 'MAILTO:' + (event_obj.organizer)
440         elif event_obj.user_id or event_obj.organizer_id:
441             event_org = event.add('organizer')
442             organizer = event_obj.organizer_id
443             if not organizer:
444                 organizer = event_obj.user_id
445             event_org.params['CN'] = [organizer.name]
446             event_org.value = 'MAILTO:' + (organizer.user_email or organizer.name)
447
448         if event_obj.alarm_id:
449             # computes alarm data
450             valarm = event.add('valarm')
451             alarm_object = self.pool.get('res.alarm')
452             alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
453             # Compute trigger data
454             interval = alarm_data['trigger_interval']
455             occurs = alarm_data['trigger_occurs']
456             duration = (occurs == 'after' and alarm_data['trigger_duration']) \
457                                             or -(alarm_data['trigger_duration'])
458             related = alarm_data['trigger_related']
459             trigger = valarm.add('TRIGGER')
460             trigger.params['related'] = [related.upper()]
461             if interval == 'days':
462                 delta = timedelta(days=duration)
463             if interval == 'hours':
464                 delta = timedelta(hours=duration)
465             if interval == 'minutes':
466                 delta = timedelta(minutes=duration)
467             trigger.value = delta
468             # Compute other details
469             valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
470
471         for attendee in event_obj.attendee_ids:
472             attendee_add = event.add('attendee')
473             attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
474             attendee_add.params['ROLE'] = [str(attendee.role)]
475             attendee_add.params['RSVP'] = [str(attendee.rsvp)]
476             attendee_add.value = 'MAILTO:' + (attendee.email or '')
477         res = cal.serialize()
478         return res
479
480     def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
481         """
482         Send mail for event invitation to event attendees.
483         @param cr: the current row, from the database cursor,
484         @param uid: the current user’s ID for security checks,
485         @param ids: List of attendee’s IDs.
486         @param email_from: Email address for user sending the mail
487         @param context: A standard dictionary for contextual values
488         @return: True
489         """
490         if context is None:
491             context = {}
492
493         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
494         for att in self.browse(cr, uid, ids, context=context):
495             sign = att.sent_by_uid and att.sent_by_uid.signature or ''
496             sign = '<br>'.join(sign and sign.split('\n') or [])
497             res_obj = att.ref
498             if res_obj:
499                 att_infos = []
500                 sub = res_obj.name
501                 other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
502
503                 for att2 in self.browse(cr, uid, other_invitation_ids):
504                     att_infos.append(((att2.user_id and att2.user_id.name) or \
505                                  (att2.partner_id and att2.partner_id.name) or \
506                                     att2.email) + ' - Status: ' + att2.state.title())
507                 body_vals = {'name': res_obj.name,
508                             'start_date': res_obj.date,
509                             'end_date': res_obj.date_deadline or False,
510                             'description': res_obj.description or '-',
511                             'location': res_obj.location or '-',
512                             'attendees': '<br>'.join(att_infos),
513                             'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
514                             'sign': sign,
515                             'company': company
516                 }
517                 body = html_invitation % body_vals
518                 if mail_to and email_from:
519                     attach = self.get_ics_file(cr, uid, res_obj, context=context)
520                     tools.email_send(
521                         email_from,
522                         mail_to,
523                         sub,
524                         body,
525                         attach=attach and [('invitation.ics', attach)] or None,
526                         subtype='html',
527                         reply_to=email_from
528                     )
529             return True
530
531     def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
532         """
533         Make entry on email and availbility on change of user_id field.
534         @param cr: the current row, from the database cursor,
535         @param uid: the current user’s ID for security checks,
536         @param ids: List of calendar attendee’s IDs.
537         @param user_id: Changed value of User id
538         @return: dictionary of value. which put value in email and availability fields.
539         """
540
541         if not user_id:
542             return {'value': {'email': ''}}
543         usr_obj = self.pool.get('res.users')
544         user = usr_obj.browse(cr, uid, user_id, *args)
545         return {'value': {'email': user.address_id.email, 'availability':user.availability}}
546
547     def do_tentative(self, cr, uid, ids, context=None, *args):
548         """ Makes event invitation as Tentative
549         @param self: The object pointer
550         @param cr: the current row, from the database cursor,
551         @param uid: the current user’s ID for security checks,
552         @param ids: List of calendar attendee’s IDs
553         @param *args: Get Tupple value
554         @param context: A standard dictionary for contextual values
555         """
556         return self.write(cr, uid, ids, {'state': 'tentative'}, context)
557
558     def do_accept(self, cr, uid, ids, context=None, *args):
559         """
560         Update state of invitation as Accepted and
561         if the invited user is other then event user it will make a copy of this event for invited user
562         @param cr: the current row, from the database cursor,
563         @param uid: the current user’s ID for security checks,
564         @param ids: List of calendar attendee’s IDs.
565         @param context: A standard dictionary for contextual values
566         @return: True
567         """
568         if context is None:
569             context = {}
570
571         for vals in self.browse(cr, uid, ids, context=context):
572             if vals.ref and vals.ref.user_id:
573                 mod_obj = self.pool.get(vals.ref._name)
574                 defaults = {'user_id': vals.user_id.id, 'organizer_id': vals.ref.user_id.id}
575                 mod_obj.copy(cr, uid, vals.ref.id, default=defaults, context=context)
576             self.write(cr, uid, vals.id, {'state': 'accepted'}, context)
577
578         return True
579
580     def do_decline(self, cr, uid, ids, context=None, *args):
581         """ Marks event invitation as Declined
582         @param self: The object pointer
583         @param cr: the current row, from the database cursor,
584         @param uid: the current user’s ID for security checks,
585         @param ids: List of calendar attendee’s IDs
586         @param *args: Get Tupple value
587         @param context: A standard dictionary for contextual values """
588         if context is None:
589             context = {}
590         return self.write(cr, uid, ids, {'state': 'declined'}, context)
591
592     def create(self, cr, uid, vals, context=None):
593         """ Overrides orm create method.
594         @param self: The object pointer
595         @param cr: the current row, from the database cursor,
596         @param uid: the current user’s ID for security checks,
597         @param vals: Get Values
598         @param context: A standard dictionary for contextual values """
599
600         if context is None:
601             context = {}
602         if not vals.get("email") and vals.get("cn"):
603             cnval = vals.get("cn").split(':')
604             email = filter(lambda x:x.__contains__('@'), cnval)
605             vals['email'] = email and email[0] or ''
606             vals['cn'] = vals.get("cn")
607         res = super(calendar_attendee, self).create(cr, uid, vals, context)
608         return res
609 calendar_attendee()
610
611 class res_alarm(osv.osv):
612     """Resource Alarm """
613     _name = 'res.alarm'
614     _description = 'Basic Alarm Information'
615
616     _columns = {
617         'name':fields.char('Name', size=256, required=True),
618         'trigger_occurs': fields.selection([('before', 'Before'), \
619                                             ('after', 'After')], \
620                                         'Triggers', required=True),
621         'trigger_interval': fields.selection([('minutes', 'Minutes'), \
622                                                 ('hours', 'Hours'), \
623                                                 ('days', 'Days')], 'Interval', \
624                                                 required=True),
625         'trigger_duration': fields.integer('Duration', required=True),
626         'trigger_related': fields.selection([('start', 'The event starts'), \
627                                             ('end', 'The event ends')], \
628                                             'Related to', required=True),
629         'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
630 are both optional, but if one occurs, so MUST the other"""),
631         'repeat': fields.integer('Repeat'),
632         'active': fields.boolean('Active', help="If the active field is set to \
633 true, it will allow you to hide the event alarm information without removing it.")
634     }
635     _defaults = {
636         'trigger_interval': 'minutes',
637         'trigger_duration': 5,
638         'trigger_occurs': 'before',
639         'trigger_related': 'start',
640         'active': 1,
641     }
642
643     def do_alarm_create(self, cr, uid, ids, model, date, context=None):
644         """
645         Create Alarm for event.
646         @param cr: the current row, from the database cursor,
647         @param uid: the current user’s ID for security checks,
648         @param ids: List of res alarm’s IDs.
649         @param model: Model name.
650         @param date: Event date
651         @param context: A standard dictionary for contextual values
652         @return: True
653         """
654         if context is None:
655             context = {}
656         alarm_obj = self.pool.get('calendar.alarm')
657         res_alarm_obj = self.pool.get('res.alarm')
658         ir_obj = self.pool.get('ir.model')
659         model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
660
661         model_obj = self.pool.get(model)
662         for data in model_obj.browse(cr, uid, ids, context=context):
663
664             basic_alarm = data.alarm_id
665             cal_alarm = data.base_calendar_alarm_id
666             if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
667                 new_res_alarm = None
668                 # Find for existing res.alarm
669                 duration = cal_alarm.trigger_duration
670                 interval = cal_alarm.trigger_interval
671                 occurs = cal_alarm.trigger_occurs
672                 related = cal_alarm.trigger_related
673                 domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
674                 alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
675                 if not alarm_ids:
676                     val = {
677                             'trigger_duration': duration,
678                             'trigger_interval': interval,
679                             'trigger_occurs': occurs,
680                             'trigger_related': related,
681                             'name': str(duration) + ' ' + str(interval) + ' '  + str(occurs)
682                            }
683                     new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
684                 else:
685                     new_res_alarm = alarm_ids[0]
686                 cr.execute('UPDATE %s ' % model_obj._table + \
687                             ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
688                             ' WHERE id=%s',
689                             (cal_alarm.id, new_res_alarm, data.id))
690
691             self.do_alarm_unlink(cr, uid, [data.id], model)
692             if basic_alarm:
693                 vals = {
694                     'action': 'display',
695                     'description': data.description,
696                     'name': data.name,
697                     'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
698                     'trigger_related': basic_alarm.trigger_related,
699                     'trigger_duration': basic_alarm.trigger_duration,
700                     'trigger_occurs': basic_alarm.trigger_occurs,
701                     'trigger_interval': basic_alarm.trigger_interval,
702                     'duration': basic_alarm.duration,
703                     'repeat': basic_alarm.repeat,
704                     'state': 'run',
705                     'event_date': data[date],
706                     'res_id': data.id,
707                     'model_id': model_id,
708                     'user_id': uid
709                  }
710                 alarm_id = alarm_obj.create(cr, uid, vals)
711                 cr.execute('UPDATE %s ' % model_obj._table + \
712                             ' SET base_calendar_alarm_id=%s, alarm_id=%s '
713                             ' WHERE id=%s', \
714                             ( alarm_id, basic_alarm.id, data.id) )
715         return True
716
717     def do_alarm_unlink(self, cr, uid, ids, model, context=None):
718         """
719         Delete alarm specified in ids
720         @param cr: the current row, from the database cursor,
721         @param uid: the current user’s ID for security checks,
722         @param ids: List of res alarm’s IDs.
723         @param model: Model name for which alarm is to be cleared.
724         @return: True
725         """
726         if context is None:
727             context = {}
728         alarm_obj = self.pool.get('calendar.alarm')
729         ir_obj = self.pool.get('ir.model')
730         model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
731         model_obj = self.pool.get(model)
732         for datas in model_obj.browse(cr, uid, ids, context=context):
733             alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
734             if alarm_ids:
735                 alarm_obj.unlink(cr, uid, alarm_ids)
736                 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
737                             where id=%%s' % model_obj._table,(datas.id,))
738         return True
739
740 res_alarm()
741
742 class calendar_alarm(osv.osv):
743     _name = 'calendar.alarm'
744     _description = 'Event alarm information'
745     _inherit = 'res.alarm'
746     __attribute__ = {}
747
748     _columns = {
749         'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
750         'name': fields.char('Summary', size=124, help="""Contains the text to be \
751                      used as the message subject for email \
752                      or contains the text to be used for display"""),
753         'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
754                 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
755                 required=True, help="Defines the action to be invoked when an alarm is triggered"),
756         'description': fields.text('Description', help='Provides a more complete \
757                             description of the calendar component, than that \
758                             provided by the "SUMMARY" property'),
759         'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
760                                       'alarm_id', 'attendee_id', 'Attendees', readonly=True),
761         'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
762                      which is rendered when the alarm is triggered for audio,
763                     * File which is intended to be sent as message attachments for email,
764                     * Points to a procedure resource, which is invoked when\
765                       the alarm is triggered for procedure."""),
766         'res_id': fields.integer('Resource ID'),
767         'model_id': fields.many2one('ir.model', 'Model'),
768         'user_id': fields.many2one('res.users', 'Owner'),
769         'event_date': fields.datetime('Event Date'),
770         'event_end_date': fields.datetime('Event End Date'),
771         'trigger_date': fields.datetime('Trigger Date', readonly="True"),
772         'state':fields.selection([
773                     ('draft', 'Draft'),
774                     ('run', 'Run'),
775                     ('stop', 'Stop'),
776                     ('done', 'Done'),
777                 ], 'State', select=True, readonly=True),
778      }
779
780     _defaults = {
781         'action': 'email',
782         'state': 'run',
783      }
784
785     def create(self, cr, uid, vals, context=None):
786         """
787         Overrides orm create method.
788         @param self: The object pointer
789         @param cr: the current row, from the database cursor,
790         @param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
791         @param context: A standard dictionary for contextual values
792         @return: new record id for calendar_alarm.
793         """
794         if context is None:
795             context = {}
796         event_date = vals.get('event_date', False)
797         if event_date:
798             dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
799             if vals['trigger_interval'] == 'days':
800                 delta = timedelta(days=vals['trigger_duration'])
801             if vals['trigger_interval'] == 'hours':
802                 delta = timedelta(hours=vals['trigger_duration'])
803             if vals['trigger_interval'] == 'minutes':
804                 delta = timedelta(minutes=vals['trigger_duration'])
805             trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
806             vals['trigger_date'] = trigger_date
807         res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
808         return res
809
810     def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
811                        context=None):
812         """Scheduler for event reminder
813         @param self: The object pointer
814         @param cr: the current row, from the database cursor,
815         @param uid: the current user’s ID for security checks,
816         @param ids: List of calendar alarm’s IDs.
817         @param use_new_cursor: False or the dbname
818         @param context: A standard dictionary for contextual values
819         """
820         return True # XXX FIXME REMOVE THIS AFTER FIXING get_recurrent_dates!!
821         if context is None:
822             context = {}
823         current_datetime = datetime.now()
824         request_obj = self.pool.get('res.request')
825         alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
826
827         mail_to = []
828
829         for alarm in self.browse(cr, uid, alarm_ids, context=context):
830             next_trigger_date = None
831             update_vals = {}
832             model_obj = self.pool.get(alarm.model_id.model)
833             res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
834             re_dates = []
835
836             if res_obj.rrule:
837                 event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
838                 recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
839
840                 trigger_interval = alarm.trigger_interval
841                 if trigger_interval == 'days':
842                     delta = timedelta(days=alarm.trigger_duration)
843                 if trigger_interval == 'hours':
844                     delta = timedelta(hours=alarm.trigger_duration)
845                 if trigger_interval == 'minutes':
846                     delta = timedelta(minutes=alarm.trigger_duration)
847                 delta = alarm.trigger_occurs == 'after' and delta or -delta
848
849                 for rdate in recurrent_dates:
850                     if rdate + delta > current_datetime:
851                         break
852                     if rdate + delta <= current_datetime:
853                         re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
854                 rest_dates = recurrent_dates[len(re_dates):]
855                 next_trigger_date = rest_dates and rest_dates[0] or None
856
857             else:
858                 re_dates = [alarm.trigger_date]
859
860             for r_date in re_dates:
861                 ref = alarm.model_id.model + ',' + str(alarm.res_id)
862
863                 # search for alreay sent requests
864                 if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
865                     continue
866
867                 if alarm.action == 'display':
868                     value = {
869                        'name': alarm.name,
870                        'act_from': alarm.user_id.id,
871                        'act_to': alarm.user_id.id,
872                        'body': alarm.description,
873                        'trigger_date': r_date,
874                        'ref_doc1': ref
875                     }
876                     request_id = request_obj.create(cr, uid, value)
877                     request_ids = [request_id]
878                     for attendee in res_obj.attendee_ids:
879                         if attendee.user_id:
880                             value['act_to'] = attendee.user_id.id
881                             request_id = request_obj.create(cr, uid, value)
882                             request_ids.append(request_id)
883                     request_obj.request_send(cr, uid, request_ids)
884
885                 if alarm.action == 'email':
886                     sub = '[Openobject Reminder] %s' % (alarm.name)
887                     body = """
888 Event: %s
889 Event Date: %s
890 Description: %s
891
892 From:
893       %s
894
895 ----
896 %s
897
898 """  % (alarm.name, alarm.trigger_date, alarm.description, \
899                         alarm.user_id.name, alarm.user_id.signature)
900                     mail_to = [alarm.user_id.address_id.email]
901                     for att in alarm.attendee_ids:
902                         mail_to.append(att.user_id.address_id.email)
903                     if mail_to:
904                         tools.email_send(
905                             tools.config.get('email_from', False),
906                             mail_to,
907                             sub,
908                             body
909                         )
910             if next_trigger_date:
911                 update_vals.update({'trigger_date': next_trigger_date})
912             else:
913                 update_vals.update({'state': 'done'})
914             self.write(cr, uid, [alarm.id], update_vals)
915         return True
916
917 calendar_alarm()
918
919
920 class calendar_event(osv.osv):
921     _name = "calendar.event"
922     _description = "Calendar Event"
923     __attribute__ = {}
924
925     def _tz_get(self, cr, uid, context=None):
926         return [(x.lower(), x) for x in pytz.all_timezones]
927
928     def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
929         """Returns duration and/or end date based on values passed
930         @param self: The object pointer
931         @param cr: the current row, from the database cursor,
932         @param uid: the current user’s ID for security checks,
933         @param ids: List of calendar event’s IDs.
934         @param start_date: Starting date
935         @param duration: Duration between start date and end date
936         @param end_date: Ending Datee
937         @param context: A standard dictionary for contextual values
938         """
939         if context is None:
940             context = {}
941
942         value = {}
943         if not start_date:
944             return value
945         if not end_date and not duration:
946             duration = 1.00
947             value['duration'] = duration
948
949         if allday: # For all day event
950             value = {'duration': 24}
951             duration = 24.0
952             if start_date:
953                 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
954                 start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
955                 value['date'] = start_date
956
957
958         start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
959         if end_date and not duration:
960             end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
961             diff = end - start
962             duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
963             value['duration'] = round(duration, 2)
964         elif not end_date:
965             end = start + timedelta(hours=duration)
966             value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
967         elif end_date and duration and not allday:
968             # we have both, keep them synchronized:
969             # set duration based on end_date (arbitrary decision: this avoid
970             # getting dates like 06:31:48 instead of 06:32:00)
971             end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
972             diff = end - start
973             duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
974             value['duration'] = round(duration, 2)
975
976         return {'value': value}
977
978     def unlink_events(self, cr, uid, ids, context=None):
979         """
980         This function deletes event which are linked with the event with recurrent_uid
981                 (Removes the events which refers to the same UID value)
982         """
983         if context is None:
984             context = {}
985         for event_id in ids:
986             cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
987             r_ids = map(lambda x: x[0], cr.fetchall())
988             self.unlink(cr, uid, r_ids, context=context)
989         return True
990
991     def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
992         """
993         Sets values of fields that defines event recurrence from the value of rrule string
994         @param self: The object pointer
995         @param cr: the current row, from the database cursor,
996         @param id: List of calendar event's ids.
997         @param context: A standard dictionary for contextual values
998         @return: dictionary of rrule value.
999         """
1000         if context is None:
1001             context = {}
1002         cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
1003                     mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
1004                     day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
1005
1006         if not value:
1007             cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
1008             return True
1009         val = {}
1010         for part in value.split(';'):
1011             if part.lower().__contains__('freq') and len(value.split(';')) <=2:
1012                 rrule_type = part.lower()[5:]
1013                 break
1014             else:
1015                 rrule_type = 'custom'
1016                 break
1017         ans = value.split(';')
1018         for i in ans:
1019             val[i.split('=')[0].lower()] = i.split('=')[1].lower()
1020         if not val.get('interval'):
1021             rrule_type = 'custom'
1022         elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
1023             rrule_type = 'custom'
1024
1025         qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
1026         qry_args = [ rrule_type, ]
1027         new_val = val.copy()
1028         for k, v in val.items():
1029             if  val['freq'] == 'weekly' and val.get('byday'):
1030                 for day in val['byday'].split(','):
1031                     new_val[day] = True
1032                 val.pop('byday')
1033
1034             if val.get('until'):
1035                 until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
1036                 new_val['end_date'] = until.strftime('%Y-%m-%d')
1037                 val.pop('until')
1038                 new_val.pop('until')
1039
1040             if val.get('bymonthday'):
1041                 new_val['day'] = val.get('bymonthday')
1042                 val.pop('bymonthday')
1043                 new_val['select1'] = 'date'
1044                 new_val.pop('bymonthday')
1045
1046             if val.get('byday'):
1047                 d = val.get('byday')
1048                 if '-' in d:
1049                     new_val['byday'] = d[:2]
1050                     new_val['week_list'] = d[2:4].upper()
1051                 else:
1052                     new_val['byday'] = d[:1]
1053                     new_val['week_list'] = d[1:3].upper()
1054                 new_val['select1'] = 'day'
1055
1056             if val.get('bymonth'):
1057                 new_val['month_list'] = val.get('bymonth')
1058                 val.pop('bymonth')
1059                 new_val.pop('bymonth')
1060
1061         for k, v in new_val.items():
1062             qry += ", %s=%%s" % k
1063             qry_args.append(v)
1064
1065         qry = qry + " where id=%s"
1066         qry_args.append(id)
1067         cr.execute(qry, qry_args)
1068         return True
1069
1070     def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
1071         """
1072         Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.
1073         @param self: The object pointer
1074         @param cr: the current row, from the database cursor,
1075         @param id: List of calendar event's ids.
1076         @param context: A standard dictionary for contextual values
1077         @return: dictionary of rrule value.
1078         """
1079         
1080         result = {}
1081         for datas in self.read(cr, uid, ids, context=context):
1082             event = datas['id']
1083             if datas.get('rrule_type'):
1084                 if datas.get('rrule_type') == 'none':
1085                     result[event] = False
1086                     cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
1087                 if datas.get('rrule_type') :
1088                     if datas.get('interval', 0) < 0:
1089                         raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
1090                     if datas.get('count', 0) < 0:
1091                         raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
1092                     rrule_custom = self.compute_rule_string(cr, uid, datas, \
1093                                                          context=context)
1094                     result[event] = rrule_custom
1095                 else:
1096                     result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
1097
1098         for id, myrule in result.items():
1099             #Remove the events generated from recurrent event
1100             if not myrule:
1101                 self.unlink_events(cr, uid, [id], context=context)
1102         return result
1103
1104     _columns = {
1105         'id': fields.integer('ID'),
1106         'sequence': fields.integer('Sequence'),
1107         'name': fields.char('Description', size=64, required=False, states={'done': [('readonly', True)]}),
1108         'date': fields.datetime('Date', states={'done': [('readonly', True)]}),
1109         'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}),
1110         'create_date': fields.datetime('Created', readonly=True),
1111         'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
1112         'description': fields.text('Description', states={'done': [('readonly', True)]}),
1113         'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
1114              ('confidential', 'Confidential')], 'Mark as', states={'done': [('readonly', True)]}),
1115         'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
1116         'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
1117                                                 'Show as', states={'done': [('readonly', True)]}),
1118         'base_calendar_url': fields.char('Caldav URL', size=264),
1119         'state': fields.selection([('tentative', 'Tentative'),
1120                         ('confirmed', 'Confirmed'),
1121                         ('cancelled', 'Cancelled')], 'State', readonly=True),
1122         'exdate': fields.text('Exception Date/Times', help="This property \
1123 defines the list of date/time exceptions for a recurring calendar component."),
1124         'exrule': fields.char('Exception Rule', size=352, help="Defines a \
1125 rule or repeating pattern of time to exclude from the recurring rule."),
1126         'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
1127                                     string='Recurrent Rule', store=True, \
1128                                     fnct_inv=_set_rrulestring, help='Defines a\
1129  rule or repeating pattern for recurring events\n\
1130 e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
1131         FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
1132         'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
1133                             ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
1134                             ('yearly', 'Yearly'),],
1135                             'Recurrency', states={'done': [('readonly', True)]},
1136                             help="Let the event automatically repeat at that interval"),
1137         'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
1138                         help="Set an alarm at this time, before the event occurs" ),
1139         'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
1140         'recurrent_uid': fields.integer('Recurrent ID'),
1141         'recurrent_id': fields.datetime('Recurrent ID date'),
1142         'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
1143         'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
1144         'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
1145         'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
1146         'freq': fields.selection([('None', 'No Repeat'),
1147                                 ('hourly', 'Hours'),
1148                                 ('daily', 'Days'),
1149                                 ('weekly', 'Weeks'),
1150                                 ('monthly', 'Months'),
1151                                 ('yearly', 'Years'), ], 'Frequency'),
1152
1153         'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
1154         'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
1155         'count': fields.integer('Repeat', help="Repeat x times"),
1156         'mo': fields.boolean('Mon'),
1157         'tu': fields.boolean('Tue'),
1158         'we': fields.boolean('Wed'),
1159         'th': fields.boolean('Thu'),
1160         'fr': fields.boolean('Fri'),
1161         'sa': fields.boolean('Sat'),
1162         'su': fields.boolean('Sun'),
1163         'select1': fields.selection([('date', 'Date of month'),
1164                                     ('day', 'Day of month')], 'Option'),
1165         'day': fields.integer('Date of month'),
1166         'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1167                                    ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1168                                    ('FR', 'Friday'), ('SA', 'Saturday'), \
1169                                    ('SU', 'Sunday')], 'Weekday'),
1170         'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1171                                    ('3', 'Third'), ('4', 'Fourth'), \
1172                                    ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
1173         'month_list': fields.selection(months.items(), 'Month'),
1174         'end_date': fields.date('Repeat Until'),
1175         'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
1176                                  'event_id', 'attendee_id', 'Attendees'),
1177         'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
1178         'active': fields.boolean('Active', help="If the active field is set to \
1179          true, it will allow you to hide the event alarm information without removing it."),
1180         'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
1181         'edit_all': fields.boolean('Edit All', help="Edit all Occurrences  of recurrent Meeting."),
1182     }
1183     def default_organizer(self, cr, uid, context=None):
1184         user_pool = self.pool.get('res.users')
1185         user = user_pool.browse(cr, uid, uid, context=context)
1186         res = user.name
1187         if user.user_email:
1188             res += " <%s>" %(user.user_email)
1189         return res
1190
1191     _defaults = {
1192             'end_type' : 'forever',
1193             'state': 'tentative',
1194             'class': 'public',
1195             'show_as': 'busy',
1196             'freq': 'None',
1197             'select1': 'date',
1198             'interval': 1,
1199             'active': 1,
1200             'user_id': lambda self, cr, uid, ctx: uid,
1201             'organizer': default_organizer,
1202             'edit_all' : False,
1203             'recurrent_id' : '',
1204     }
1205
1206     def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
1207         
1208         value = {'edit_all' : edit_all}
1209         self.write(cr, uid, ids, value, context=context)
1210         return {}
1211
1212     def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
1213         """
1214         Modifies the recurring event
1215         @param cr: the current row, from the database cursor,
1216         @param uid: the current user’s ID for security checks,
1217         @param event_ids: List of crm meeting’s IDs.
1218         @param context: A standard dictionary for contextual values
1219         @return: True
1220         """
1221         for event_id in event_ids:
1222             event_id = base_calendar_id2real_id(event_id)
1223
1224             defaults.pop('id')
1225             defaults.update({'table': self._table})
1226
1227             qry = "UPDATE %(table)s set name = '%(name)s', \
1228                             date = '%(date)s', date_deadline = '%(date_deadline)s'"
1229             if defaults.get('alarm_id'):
1230                 qry += ", alarm_id = %(alarm_id)s"
1231             if defaults.get('location'):
1232                 qry += ", location = '%(location)s'"
1233             qry += "WHERE id = %s" % (event_id)
1234             cr.execute(qry, defaults)
1235
1236         return True
1237
1238     def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
1239         """Gives virtual event ids for recurring events based on value of Recurrence Rule
1240         This method gives ids of dates that comes between start date and end date of calendar views
1241         @param self: The object pointer
1242         @param cr: the current row, from the database cursor,
1243         @param uid: the current user’s ID for security checks,
1244         @param base_start_date: Get Start Date
1245         @param base_until_date: Get End Date
1246         @param limit: The Number of Results to Return """
1247         if not context:
1248             context = {}
1249
1250         virtual_id = context and context.get('virtual_id', False) or False
1251
1252         if isinstance(select, (str, int, long)):
1253             ids = [select]
1254         else:
1255             ids = select
1256         result = []
1257         recur_dict = []
1258         if ids and virtual_id:
1259             cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
1260                             m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
1261                             " m where m.id = ANY(%s)", (ids,) )
1262
1263             for data in cr.dictfetchall():
1264                 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1265                 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1266                 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1267 #                To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1268                 
1269                 if not data['rrule']:
1270                     start_date = event_date
1271                     if start_date and (event_date < start_date):
1272                         continue
1273                     if until_date and (event_date > until_date):
1274                         continue
1275                     idval = data['id']
1276                     if not data['recurrent_id']:
1277                         result.append(idval)                        
1278                     else:
1279                         ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1280                         ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1281                         if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1282                             if ls[1] == data['recurrent_id']:
1283                                 result.append(idval)
1284                         recur_dict.append(ex_id)
1285                 else:
1286                     exdate = data['exdate'] and data['exdate'].split(',') or []
1287                     rrule_str = data['rrule']
1288                     new_rrule_str = []
1289                     rrule_until_date = False
1290                     is_until = False
1291                     for rule in rrule_str.split(';'):
1292                         name, value = rule.split('=')
1293                         if name == "UNTIL":
1294                             is_until = True
1295                             value = parser.parse(value)
1296                             rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1297                             if until_date and until_date >= rrule_until_date:
1298                                 until_date = rrule_until_date
1299                             if until_date:
1300                                 value = until_date.strftime("%Y%m%d%H%M%S")
1301                         new_rule = '%s=%s' % (name, value)
1302                         new_rrule_str.append(new_rule)
1303                     if not is_until and until_date:
1304                         value = until_date.strftime("%Y%m%d%H%M%S")
1305                         name = "UNTIL"
1306                         new_rule = '%s=%s' % (name, value)
1307                         new_rrule_str.append(new_rule)
1308                     new_rrule_str = ';'.join(new_rrule_str)
1309                     rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1310                     
1311                     for r_date in rdates:
1312                         if start_date and r_date < start_date:
1313                             continue
1314                         if until_date and r_date > until_date:
1315                             continue
1316                         idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1317                         result.append(idval)
1318
1319         if result:
1320             ids = list(set(result)-set(recur_dict))
1321         if isinstance(select, (str, int, long)):
1322             return ids and ids[0] or False
1323         return ids
1324
1325     def compute_rule_string(self, cr, uid, datas, context=None, *args):
1326         """
1327         Compute rule string according to value type RECUR of iCalendar from the values given.
1328         @param self: the object pointer
1329         @param cr: the current row, from the database cursor,
1330         @param uid: the current user’s ID for security checks,
1331         @param datas: dictionary of freq and interval value.
1332         @param context: A standard dictionary for contextual values
1333         @return: String value of the format RECUR of iCalendar
1334         """
1335
1336         weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1337         weekstring = ''
1338         monthstring = ''
1339         yearstring = ''
1340         freq=datas.get('rrule_type')
1341         if  freq == 'none':
1342             return ''
1343
1344         interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1345
1346         if freq == 'weekly':
1347             byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1348             if byday:
1349                 weekstring = ';BYDAY=' + ','.join(byday)
1350
1351         elif freq == 'monthly':
1352             if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1353                 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1354             if datas.get('select1')=='day':
1355                 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1356             elif datas.get('select1')=='date':
1357                 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1358
1359
1360         if datas.get('end_date'):
1361             datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1362         enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1363                              ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1364
1365         rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1366                             + enddate + monthstring + yearstring
1367
1368         return rrule_string
1369
1370
1371     def remove_virtual_id(self, ids):
1372         if isinstance(ids, (str, int)):
1373             return base_calendar_id2real_id(ids)
1374             
1375         if isinstance(ids, (list, tuple)):
1376             res = []
1377             for id in ids:
1378                 res.append(base_calendar_id2real_id(id))
1379             return res
1380                 
1381     def search(self, cr, uid, args, offset=0, limit=0, order=None,
1382             context=None, count=False):
1383         print "limit begin", limit
1384         print "count", count
1385         args_without_date = []
1386         start_date = False
1387         until_date = False
1388
1389         for arg in args:
1390             if arg[0] == "id":
1391                 new_id = self.remove_virtual_id(arg[2])
1392                 new_arg = (arg[0], arg[1], new_id)
1393                 args_without_date.append(new_arg)
1394             elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1395                 args_without_date.append(arg)
1396             else:
1397                 if arg[1] in ('>', '>='):
1398                     if start_date:
1399                         continue
1400                     start_date = arg[2]
1401                 elif arg[1] in ('<', '<='):
1402                     if until_date:
1403                         continue
1404                     until_date = arg[2]
1405         
1406         res = super(calendar_event, self).search(cr, uid, args_without_date, \
1407                                  0, 0, order, context, count=False)
1408         res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
1409         print "limit", limit
1410         print "offset", offset
1411         print "offset + limit", offset+limit
1412         if count:
1413             print "len" , len(res)
1414             return len(res)
1415         elif limit:
1416             r = res[offset:offset+limit]
1417         else:
1418             r = res
1419         print "result", r
1420         print "longueur", len(r)
1421         return r
1422
1423
1424     def get_edit_all(self, cr, uid, id, vals=None):
1425         """
1426             return true if we have to edit all meeting from the same recurrent
1427             or only on occurency
1428         """
1429         meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1430         if(vals and 'edit_all' in vals): #we jsut check edit_all
1431             return vals['edit_all']
1432         else: #it's a recurrent event and edit_all is already check
1433             return meeting['recurrency'] and meeting['edit_all']
1434
1435
1436
1437
1438     def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1439         if context is None:
1440             context = {}
1441         if isinstance(ids, (str, int, long)):
1442             select = [ids]
1443         else:
1444             select = ids
1445         new_ids = []
1446         res = False
1447         for event_id in select:
1448             real_event_id = base_calendar_id2real_id(event_id)
1449
1450
1451             if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1452                 event_id = real_event_id
1453
1454             #if edit one instance of a reccurrent id
1455             if len(str(event_id).split('-')) > 1:
1456                 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1457                                                     'rrule', 'duration', 'exdate'])
1458                 if data.get('rrule'):
1459                     data.update(vals)
1460                     data.update({
1461                         'recurrent_uid': real_event_id,
1462                         'recurrent_id': data.get('date'),
1463                         'rrule_type': 'none',
1464                         'rrule': '',
1465                         'edit_all': False,
1466                         'recurrency' : False,
1467                         })
1468
1469                     new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1470
1471                     date_new = event_id.split('-')[1]
1472                     date_new = time.strftime("%Y%m%dT%H%M%S", \
1473                                  time.strptime(date_new, "%Y%m%d%H%M%S"))
1474                     exdate = (data['exdate'] and (data['exdate'] + ',')  or '') + date_new
1475                     res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1476
1477                     context.update({'active_id': new_id, 'active_ids': [new_id]})
1478                     continue
1479             if not real_event_id in new_ids:
1480                 new_ids.append(real_event_id)
1481
1482         if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1483             vals['vtimezone'] = vals['vtimezone'][40:]
1484
1485         updated_vals = self.onchange_dates(cr, uid, new_ids,
1486             vals.get('date', False),
1487             vals.get('duration', False),
1488             vals.get('date_deadline', False),
1489             vals.get('allday', False),
1490             context=context)
1491         vals.update(updated_vals.get('value', {}))
1492
1493         if not 'edit_all' in vals:
1494             vals['edit_all'] = False
1495
1496         if new_ids:
1497             res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1498
1499         if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1500                 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1501             # change alarm details
1502             alarm_obj = self.pool.get('res.alarm')
1503             alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1504         return res or True and False
1505
1506     def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1507         if isinstance(ids, (str, int, long)):
1508             select = [ids]
1509         else:
1510             select = ids
1511         select = map(lambda x: base_calendar_id2real_id(x), select)
1512         res = super(calendar_event, self).browse(cr, uid, select, context, \
1513                                                     list_class, fields_process)
1514         if isinstance(ids, (str, int, long)):
1515             return res and res[0] or False
1516
1517         return res
1518     
1519     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1520         if not context:
1521             context = {}
1522             
1523         if 'date' in groupby:
1524              raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
1525          
1526         virtual_id = context.get('virtual_id', False)
1527         
1528         context.update({'virtual_id': False})
1529         res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
1530         for re in res:
1531             #remove the count, since the value is not consistent with the result of the search when expand the group
1532             for groupname in groupby:
1533                 if re.get(groupname + "_count"):
1534                     del re[groupname + "_count"]
1535             re.get('__context').update({'virtual_id' : virtual_id})
1536         return res
1537
1538     def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1539         # FIXME This whole id mangling has to go!
1540         if context is None:
1541             context = {}
1542         
1543
1544
1545         if isinstance(ids, (str, int, long)):
1546             select = [ids]
1547         else:
1548             select = ids
1549         select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1550         result = []
1551         if fields and 'date' not in fields:
1552             fields.append('date')
1553         if fields and 'duration' not in fields:
1554             fields.append('duration')
1555
1556
1557         for base_calendar_id, real_id in select:                
1558             #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1559             res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1560            
1561             if not res :
1562                 continue
1563             ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1564             if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1565                 res['date'] = ls[1]
1566                 res['date_deadline'] = ls[2]
1567             res['id'] = base_calendar_id
1568
1569             result.append(res)
1570         if isinstance(ids, (str, int, long)):
1571             return result and result[0] or False
1572         
1573         return result
1574
1575     def copy(self, cr, uid, id, default=None, context=None):
1576         if context is None:
1577             context = {}
1578         res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1579         alarm_obj = self.pool.get('res.alarm')
1580         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1581
1582         return res
1583
1584     def unlink(self, cr, uid, ids, context=None):
1585         res = False
1586         for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1587             event_id = event_datas['id']
1588
1589             if self.get_edit_all(cr, uid, event_id, vals=None):
1590                 event_id = base_calendar_id2real_id(event_id)
1591
1592             if isinstance(event_id, (int, long)):
1593                 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1594                 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1595                 self.unlink_events(cr, uid, [event_id], context=context)
1596             else:
1597                 str_event, date_new = event_id.split('-')
1598                 event_id = int(str_event)
1599                 if event_datas['rrule']:
1600                     # Remove one of the recurrent event
1601                     date_new = time.strftime("%Y%m%dT%H%M%S", \
1602                                  time.strptime(date_new, "%Y%m%d%H%M%S"))
1603                     exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',')  or '') + date_new
1604                     res = self.write(cr, uid, [event_id], {'exdate': exdate})
1605                 else:
1606                     res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1607                     self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1608                     self.unlink_events(cr, uid, [event_id], context=context)
1609         return res
1610
1611     def create(self, cr, uid, vals, context=None):
1612         if context is None:
1613             context = {}
1614
1615         virtual_id = context.get('virtual_id', False)
1616
1617         if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1618             vals['vtimezone'] = vals['vtimezone'][40:]
1619
1620         updated_vals = self.onchange_dates(cr, uid, [],
1621             vals.get('date', False),
1622             vals.get('duration', False),
1623             vals.get('date_deadline', False),
1624             vals.get('allday', False),
1625             context=context)
1626         vals.update(updated_vals.get('value', {}))
1627         res = super(calendar_event, self).create(cr, uid, vals, context)
1628         alarm_obj = self.pool.get('res.alarm')
1629         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1630        
1631         return res
1632     
1633     
1634
1635     def do_tentative(self, cr, uid, ids, context=None, *args):
1636         """ Makes event invitation as Tentative
1637         @param self: The object pointer
1638         @param cr: the current row, from the database cursor,
1639         @param uid: the current user’s ID for security checks,
1640         @param ids: List of Event IDs
1641         @param *args: Get Tupple value
1642         @param context: A standard dictionary for contextual values
1643         """
1644         return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1645
1646     def do_cancel(self, cr, uid, ids, context=None, *args):
1647         """ Makes event invitation as Tentative
1648         @param self: The object pointer
1649         @param cr: the current row, from the database cursor,
1650         @param uid: the current user’s ID for security checks,
1651         @param ids: List of Event IDs
1652         @param *args: Get Tupple value
1653         @param context: A standard dictionary for contextual values
1654         """
1655         return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1656
1657     def do_confirm(self, cr, uid, ids, context=None, *args):
1658         """ Makes event invitation as Tentative
1659         @param self: The object pointer
1660         @param cr: the current row, from the database cursor,
1661         @param uid: the current user’s ID for security checks,
1662         @param ids: List of Event IDs
1663         @param *args: Get Tupple value
1664         @param context: A standard dictionary for contextual values
1665         """
1666         return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1667
1668 calendar_event()
1669
1670 class calendar_todo(osv.osv):
1671     """ Calendar Task """
1672
1673     _name = "calendar.todo"
1674     _inherit = "calendar.event"
1675     _description = "Calendar Task"
1676
1677     def _get_date(self, cr, uid, ids, name, arg, context=None):
1678         """
1679         Get Date
1680         @param self: The object pointer
1681         @param cr: the current row, from the database cursor,
1682         @param uid: the current user’s ID for security checks,
1683         @param ids: List of calendar todo's IDs.
1684         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1685         @param context: A standard dictionary for contextual values
1686         """
1687
1688         res = {}
1689         for event in self.browse(cr, uid, ids, context=context):
1690             res[event.id] = event.date_start
1691         return res
1692
1693     def _set_date(self, cr, uid, id, name, value, arg, context=None):
1694         """
1695         Set Date
1696         @param self: The object pointer
1697         @param cr: the current row, from the database cursor,
1698         @param uid: the current user’s ID for security checks,
1699         @param id: calendar's ID.
1700         @param value: Get Value
1701         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1702         @param context: A standard dictionary for contextual values
1703         """
1704
1705         assert name == 'date'
1706         return self.write(cr, uid, id, { 'date_start': value }, context=context)
1707
1708     _columns = {
1709         'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1710                             string='Duration', store=True, type='datetime'),
1711         'duration': fields.integer('Duration'),
1712     }
1713
1714     __attribute__ = {}
1715
1716
1717 calendar_todo()
1718
1719 class ir_attachment(osv.osv):
1720     _name = 'ir.attachment'
1721     _inherit = 'ir.attachment'
1722
1723     def search_count(self, cr, user, args, context=None):
1724         """
1725         @param self: The object pointer
1726         @param cr: the current row, from the database cursor,
1727         @param user: the current user’s ID for security checks,
1728         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1729         @param context: A standard dictionary for contextual values
1730         """
1731
1732         args1 = []
1733         for arg in args:
1734             args1.append(map(lambda x:str(x).split('-')[0], arg))
1735         return super(ir_attachment, self).search_count(cr, user, args1, context)
1736
1737
1738
1739     def create(self, cr, uid, vals, context=None):
1740         if context:
1741             id = context.get('default_res_id', False)
1742             context.update({'default_res_id' : base_calendar_id2real_id(id)})
1743         return super(ir_attachment, self).create(cr, uid, vals, context=context)
1744
1745     def search(self, cr, uid, args, offset=0, limit=None, order=None,
1746             context=None, count=False):
1747         """
1748         @param self: The object pointer
1749         @param cr: the current row, from the database cursor,
1750         @param uid: the current user’s ID for security checks,
1751         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1752         @param offset: The Number of Results to pass,
1753         @param limit: The Number of Results to Return,
1754         @param context: A standard dictionary for contextual values
1755         """
1756
1757         new_args = args
1758         for i, arg in enumerate(new_args):
1759             if arg[0] == 'res_id':
1760                 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1761
1762         return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1763                             limit=limit, order=order, context=context, count=False)
1764 ir_attachment()
1765
1766 class ir_values(osv.osv):
1767     _inherit = 'ir.values'
1768
1769     def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1770             isobject=False, meta=False, preserve_user=False, company=False):
1771         """
1772         Set IR Values
1773         @param self: The object pointer
1774         @param cr: the current row, from the database cursor,
1775         @param uid: the current user’s ID for security checks,
1776         @param model: Get The Model
1777         """
1778
1779         new_model = []
1780         for data in models:
1781             if type(data) in (list, tuple):
1782                 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1783             else:
1784                 new_model.append(data)
1785         return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1786                     value, replace, isobject, meta, preserve_user, company)
1787
1788     def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1789              res_id_req=False, without_user=True, key2_req=True):
1790         """
1791         Get IR Values
1792         @param self: The object pointer
1793         @param cr: the current row, from the database cursor,
1794         @param uid: the current user’s ID for security checks,
1795         @param model: Get The Model
1796         """
1797         if context is None:
1798             context = {}
1799         new_model = []
1800         for data in models:
1801             if type(data) in (list, tuple):
1802                 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1803             else:
1804                 new_model.append(data)
1805         return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1806                          meta, context, res_id_req, without_user, key2_req)
1807
1808 ir_values()
1809
1810 class ir_model(osv.osv):
1811
1812     _inherit = 'ir.model'
1813
1814     def read(self, cr, uid, ids, fields=None, context=None,
1815             load='_classic_read'):
1816         """
1817         Overrides orm read method.
1818         @param self: The object pointer
1819         @param cr: the current row, from the database cursor,
1820         @param uid: the current user’s ID for security checks,
1821         @param ids: List of IR Model’s IDs.
1822         @param context: A standard dictionary for contextual values
1823         """
1824         new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1825         if context is None:
1826             context = {}
1827         data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1828                         context=context, load=load)
1829         if data:
1830             for val in data:
1831                 val['id'] = base_calendar_id2real_id(val['id'])
1832         return isinstance(ids, (str, int, long)) and data[0] or data
1833
1834 ir_model()
1835
1836 class virtual_report_spool(web_services.report_spool):
1837
1838     def exp_report(self, db, uid, object, ids, datas=None, context=None):
1839         """
1840         Export Report
1841         @param self: The object pointer
1842         @param db: get the current database,
1843         @param uid: the current user’s ID for security checks,
1844         @param context: A standard dictionary for contextual values
1845         """
1846
1847         if object == 'printscreen.list':
1848             return super(virtual_report_spool, self).exp_report(db, uid, \
1849                             object, ids, datas, context)
1850         new_ids = []
1851         for id in ids:
1852             new_ids.append(base_calendar_id2real_id(id))
1853         if datas.get('id', False):
1854             datas['id'] = base_calendar_id2real_id(datas['id'])
1855         return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1856
1857 virtual_report_spool()
1858
1859 class res_users(osv.osv):
1860     _inherit = 'res.users'
1861
1862     def _get_user_avail(self, cr, uid, ids, context=None):
1863         """
1864         Get User Availability
1865         @param self: The object pointer
1866         @param cr: the current row, from the database cursor,
1867         @param uid: the current user’s ID for security checks,
1868         @param ids: List of res user’s IDs.
1869         @param context: A standard dictionary for contextual values
1870         """
1871
1872         current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1873         res = {}
1874         attendee_obj = self.pool.get('calendar.attendee')
1875         attendee_ids = attendee_obj.search(cr, uid, [
1876                     ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1877                     ('state', '=', 'accepted'), ('user_id', 'in', ids)
1878                     ])
1879
1880         for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1881             user_id = attendee_data['user_id']
1882             status = 'busy'
1883             res.update({user_id:status})
1884
1885         #TOCHECK: Delegated Event
1886         for user_id in ids:
1887             if user_id not in res:
1888                 res[user_id] = 'free'
1889
1890         return res
1891
1892     def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1893         """
1894         Get User Availability Function
1895         @param self: The object pointer
1896         @param cr: the current row, from the database cursor,
1897         @param uid: the current user’s ID for security checks,
1898         @param ids: List of res user’s IDs.
1899         @param context: A standard dictionary for contextual values
1900         """
1901
1902         return self._get_user_avail(cr, uid, ids, context=context)
1903
1904     _columns = {
1905             'availability': fields.function(_get_user_avail_fun, type='selection', \
1906                     selection=[('free', 'Free'), ('busy', 'Busy')], \
1907                     string='Free/Busy', method=True),
1908     }
1909
1910 res_users()
1911
1912
1913 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: