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