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