[MERGE] merge from trunk addons
[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', '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 = 100
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             count = 0
1264             for data in cr.dictfetchall():
1265                 start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
1266                 until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
1267                 if count > limit:
1268                     break
1269                 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
1270 #                To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
1271                 start_date = event_date
1272                 if not data['rrule']:
1273                     if start_date and (event_date < start_date):
1274                         continue
1275                     if until_date and (event_date > until_date):
1276                         continue
1277                     idval = real_id2base_calendar_id(data['id'], data['date'])
1278                     if not data['recurrent_id']:
1279                         result.append(idval)
1280                         count += 1
1281                     else:
1282                         ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
1283                         ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
1284                         if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1285                             if ls[1] == data['recurrent_id']:
1286                                 result.append(idval)
1287                         recur_dict.append(ex_id)
1288                 else:
1289                     exdate = data['exdate'] and data['exdate'].split(',') or []
1290                     rrule_str = data['rrule']
1291                     new_rrule_str = []
1292                     rrule_until_date = False
1293                     is_until = False
1294                     for rule in rrule_str.split(';'):
1295                         name, value = rule.split('=')
1296                         if name == "UNTIL":
1297                             is_until = True
1298                             value = parser.parse(value)
1299                             rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
1300                             if until_date and until_date >= rrule_until_date:
1301                                 until_date = rrule_until_date
1302                             if until_date:
1303                                 value = until_date.strftime("%Y%m%d%H%M%S")
1304                         new_rule = '%s=%s' % (name, value)
1305                         new_rrule_str.append(new_rule)
1306                     if not is_until and until_date:
1307                         value = until_date.strftime("%Y%m%d%H%M%S")
1308                         name = "UNTIL"
1309                         new_rule = '%s=%s' % (name, value)
1310                         new_rrule_str.append(new_rule)
1311                     new_rrule_str = ';'.join(new_rrule_str)
1312                     rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
1313                     for r_date in rdates:
1314                         if start_date and r_date < start_date:
1315                             continue
1316                         if until_date and r_date > until_date:
1317                             continue
1318                         idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
1319                         result.append(idval)
1320                         count += 1
1321         if result:
1322             ids = list(set(result)-set(recur_dict))
1323         if isinstance(select, (str, int, long)):
1324             return ids and ids[0] or False
1325         return ids
1326
1327     def compute_rule_string(self, cr, uid, datas, context=None, *args):
1328         """
1329         Compute rule string according to value type RECUR of iCalendar from the values given.
1330         @param self: the object pointer
1331         @param cr: the current row, from the database cursor,
1332         @param uid: the current user’s ID for security checks,
1333         @param datas: dictionary of freq and interval value.
1334         @param context: A standard dictionary for contextual values
1335         @return: String value of the format RECUR of iCalendar
1336         """
1337
1338         weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1339         weekstring = ''
1340         monthstring = ''
1341         yearstring = ''
1342         freq=datas.get('rrule_type')
1343         if  freq == 'none':
1344             return ''
1345             
1346         interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
1347
1348         if freq == 'weekly':
1349             byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1350             if byday:
1351                 weekstring = ';BYDAY=' + ','.join(byday)
1352
1353         elif freq == 'monthly':
1354             if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1355                 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1356             if datas.get('select1')=='day':
1357                 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1358             elif datas.get('select1')=='date':
1359                 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1360
1361        
1362         if datas.get('end_date'):
1363             datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
1364         enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
1365                              ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1366
1367         rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
1368                             + enddate + monthstring + yearstring
1369
1370         return rrule_string
1371
1372     def search(self, cr, uid, args, offset=0, limit=100, order=None,
1373             context=None, count=False):
1374         """
1375         Overrides orm search method.
1376         @param cr: the current row, from the database cursor,
1377         @param user: the current user’s ID for security checks,
1378         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1379         @param offset: The Number of Results to Pass
1380         @param limit: The Number of Results to Return
1381         @param context: A standard dictionary for contextual values
1382         @param count: If its True the method returns number of records instead of ids
1383         @return: List of id
1384         """
1385         args_without_date = []
1386         start_date = False
1387         until_date = False
1388
1389         for arg in args:
1390             if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
1391                 args_without_date.append(arg)
1392             else:
1393                 if arg[1] in ('>', '>='):
1394                     if start_date:
1395                         continue
1396                     start_date = arg[2]
1397                 elif arg[1] in ('<', '<='):
1398                     if until_date:
1399                         continue
1400                     until_date = arg[2]
1401         res = super(calendar_event, self).search(cr, uid, args_without_date, \
1402                                  offset, limit, order, context, count)
1403
1404         res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
1405         return res
1406     
1407
1408     def get_edit_all(self, cr, uid, id, vals=None):
1409         """
1410             return true if we have to edit all meeting from the same recurrent
1411             or only on occurency
1412         """
1413         meeting = self.read(cr,uid, id, ['edit_all', 'recurrency'] )
1414         if(vals and 'edit_all' in vals): #we jsut check edit_all
1415             return vals['edit_all']
1416         else: #it's a recurrent event and edit_all is already check
1417             return meeting['recurrency'] and meeting['edit_all'] 
1418
1419
1420         
1421
1422     def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1423         """
1424         Overrides orm write method.
1425         @param self: the object pointer
1426         @param cr: the current row, from the database cursor,
1427         @param uid: the current user’s ID for security checks,
1428         @param ids: List of crm meeting's ids
1429         @param vals: Dictionary of field value.
1430         @param context: A standard dictionary for contextual values
1431         @return: True
1432         """
1433         if context is None:
1434             context = {}
1435         if isinstance(ids, (str, int, long)):
1436             select = [ids]
1437         else:
1438             select = ids
1439         new_ids = []
1440         res = False
1441         for event_id in select:
1442             real_event_id = base_calendar_id2real_id(event_id)
1443             
1444
1445             if(self.get_edit_all(cr, uid, event_id, vals=vals)):
1446                 event_id = real_event_id
1447             
1448             
1449             if len(str(event_id).split('-')) > 1:
1450                 data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
1451                                                     'rrule', 'duration', 'exdate'])
1452                 if data.get('rrule'):
1453                     data.update(vals)
1454                     data.update({
1455                         'recurrent_uid': real_event_id,
1456                         'recurrent_id': data.get('date'),
1457                         'rrule_type': 'none',
1458                         'rrule': '',
1459                         'edit_all': False,
1460                         'recurrency' : False,
1461                         })
1462                     
1463                     new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
1464                     
1465                     date_new = event_id.split('-')[1]
1466                     date_new = time.strftime("%Y%m%dT%H%M%S", \
1467                                  time.strptime(date_new, "%Y%m%d%H%M%S"))
1468                     exdate = (data['exdate'] and (data['exdate'] + ',')  or '') + date_new
1469                     res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
1470                     
1471                     context.update({'active_id': new_id, 'active_ids': [new_id]})
1472                     continue
1473             if not real_event_id in new_ids:
1474                 new_ids.append(real_event_id)
1475
1476         if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1477             vals['vtimezone'] = vals['vtimezone'][40:]
1478
1479         updated_vals = self.onchange_dates(cr, uid, new_ids,
1480             vals.get('date', False),
1481             vals.get('duration', False),
1482             vals.get('date_deadline', False),
1483             vals.get('allday', False),
1484             context=context)
1485         vals.update(updated_vals.get('value', {}))
1486
1487         if not 'edit_all' in vals:
1488             vals['edit_all'] = False
1489
1490         if new_ids:
1491             res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
1492
1493         if ('alarm_id' in vals or 'base_calendar_alarm_id' in vals)\
1494                 or ('date' in vals or 'duration' in vals or 'date_deadline' in vals):
1495             # change alarm details
1496             alarm_obj = self.pool.get('res.alarm')
1497             alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1498         return res
1499
1500     def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
1501         """
1502         Overrides orm browse method.
1503         @param self: the object pointer
1504         @param cr: the current row, from the database cursor,
1505         @param uid: the current user’s ID for security checks,
1506         @param ids: List of crm meeting's ids
1507         @param context: A standard dictionary for contextual values
1508         @return: the object list.
1509         """
1510         if isinstance(ids, (str, int, long)):
1511             select = [ids]
1512         else:
1513             select = ids
1514         select = map(lambda x: base_calendar_id2real_id(x), select)
1515         res = super(calendar_event, self).browse(cr, uid, select, context, \
1516                                                     list_class, fields_process)
1517         if isinstance(ids, (str, int, long)):
1518             return res and res[0] or False
1519
1520         return res
1521
1522     def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1523         """
1524         Overrides orm Read method.Read List of fields for calendar event.
1525         @param cr: the current row, from the database cursor,
1526         @param user: the current user’s ID for security checks,
1527         @param ids: List of calendar event's id.
1528         @param fields: List of fields.
1529         @param context: A standard dictionary for contextual values
1530         @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
1531         """
1532         # FIXME This whole id mangling has to go!
1533         if context is None:
1534             context = {}
1535
1536         if isinstance(ids, (str, int, long)):
1537             select = [ids]
1538         else:
1539             select = ids
1540         select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1541         result = []
1542         if fields and 'date' not in fields:
1543             fields.append('date')
1544         if fields and 'duration' not in fields:
1545             fields.append('duration')
1546
1547
1548         for base_calendar_id, real_id in select:
1549             #REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
1550             res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
1551             if not res :
1552                 continue
1553             ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
1554             if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1555                 res['date'] = ls[1]
1556                 res['date_deadline'] = ls[2]
1557             res['id'] = base_calendar_id
1558
1559             result.append(res)
1560         if isinstance(ids, (str, int, long)):
1561             return result and result[0] or False
1562         return result
1563
1564     def copy(self, cr, uid, id, default=None, context=None):
1565         """
1566         Duplicate record on specified id.
1567         @param self: the object pointer.
1568         @param cr: the current row, from the database cursor,
1569         @param id: id of record from which we duplicated.
1570         @param context: A standard dictionary for contextual values
1571         @return: Duplicate record id.
1572         """
1573         if context is None:
1574             context = {}
1575         res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1576         alarm_obj = self.pool.get('res.alarm')
1577         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1578
1579         return res
1580
1581     def unlink(self, cr, uid, ids, context=None):
1582         """
1583         Deletes records specified in ids.
1584         @param self: the object pointer.
1585         @param cr: the current row, from the database cursor,
1586         @param id: List of calendar event's id.
1587         @param context: A standard dictionary for contextual values
1588         @return: True
1589         """
1590         res = False
1591         for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
1592             event_id = event_datas['id']
1593             
1594             if self.get_edit_all(cr, uid, event_id, vals=None):
1595                 event_id = base_calendar_id2real_id(event_id)
1596             
1597             if isinstance(event_id, (int, long)):
1598                 res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
1599                 self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1600                 self.unlink_events(cr, uid, [event_id], context=context)
1601             else:
1602                 str_event, date_new = event_id.split('-')
1603                 event_id = int(str_event)
1604                 if event_datas['rrule']:
1605                     # Remove one of the recurrent event
1606                     date_new = time.strftime("%Y%m%dT%H%M%S", \
1607                                  time.strptime(date_new, "%Y%m%d%H%M%S"))
1608                     exdate = (event_datas['exdate'] and (event_datas['exdate'] + ',')  or '') + date_new
1609                     res = self.write(cr, uid, [event_id], {'exdate': exdate})
1610                 else:
1611                     res = super(calendar_event, self).unlink(cr, uid, [event_id], context=context)
1612                     self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
1613                     self.unlink_events(cr, uid, [event_id], context=context)
1614         return res
1615
1616     def create(self, cr, uid, vals, context=None):
1617         """
1618         Create new record.
1619         @param self: the object pointer
1620         @param cr: the current row, from the database cursor,
1621         @param uid: the current user’s ID for security checks,
1622         @param vals: dictionary of every field value.
1623         @param context: A standard dictionary for contextual values
1624         @return: new created record id.
1625         """
1626         if context is None:
1627             context = {}
1628
1629         if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
1630             vals['vtimezone'] = vals['vtimezone'][40:]
1631
1632         updated_vals = self.onchange_dates(cr, uid, [],
1633             vals.get('date', False),
1634             vals.get('duration', False),
1635             vals.get('date_deadline', False),
1636             vals.get('allday', False),
1637             context=context)
1638         vals.update(updated_vals.get('value', {}))
1639
1640         res = super(calendar_event, self).create(cr, uid, vals, context)
1641         alarm_obj = self.pool.get('res.alarm')
1642         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
1643         return res
1644
1645     def do_tentative(self, cr, uid, ids, context=None, *args):
1646         """ Makes event invitation as Tentative
1647         @param self: The object pointer
1648         @param cr: the current row, from the database cursor,
1649         @param uid: the current user’s ID for security checks,
1650         @param ids: List of Event IDs
1651         @param *args: Get Tupple value
1652         @param context: A standard dictionary for contextual values
1653         """
1654         return self.write(cr, uid, ids, {'state': 'tentative'}, context)
1655
1656     def do_cancel(self, cr, uid, ids, context=None, *args):
1657         """ Makes event invitation as Tentative
1658         @param self: The object pointer
1659         @param cr: the current row, from the database cursor,
1660         @param uid: the current user’s ID for security checks,
1661         @param ids: List of Event IDs
1662         @param *args: Get Tupple value
1663         @param context: A standard dictionary for contextual values
1664         """
1665         return self.write(cr, uid, ids, {'state': 'cancelled'}, context)
1666
1667     def do_confirm(self, cr, uid, ids, context=None, *args):
1668         """ Makes event invitation as Tentative
1669         @param self: The object pointer
1670         @param cr: the current row, from the database cursor,
1671         @param uid: the current user’s ID for security checks,
1672         @param ids: List of Event IDs
1673         @param *args: Get Tupple value
1674         @param context: A standard dictionary for contextual values
1675         """
1676         return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
1677
1678 calendar_event()
1679
1680 class calendar_todo(osv.osv):
1681     """ Calendar Task """
1682
1683     _name = "calendar.todo"
1684     _inherit = "calendar.event"
1685     _description = "Calendar Task"
1686
1687     def _get_date(self, cr, uid, ids, name, arg, context=None):
1688         """
1689         Get Date
1690         @param self: The object pointer
1691         @param cr: the current row, from the database cursor,
1692         @param uid: the current user’s ID for security checks,
1693         @param ids: List of calendar todo's IDs.
1694         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1695         @param context: A standard dictionary for contextual values
1696         """
1697
1698         res = {}
1699         for event in self.browse(cr, uid, ids, context=context):
1700             res[event.id] = event.date_start
1701         return res
1702
1703     def _set_date(self, cr, uid, id, name, value, arg, context=None):
1704         """
1705         Set Date
1706         @param self: The object pointer
1707         @param cr: the current row, from the database cursor,
1708         @param uid: the current user’s ID for security checks,
1709         @param id: calendar's ID.
1710         @param value: Get Value
1711         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1712         @param context: A standard dictionary for contextual values
1713         """
1714         
1715         assert name == 'date'
1716         return self.write(cr, uid, id, { 'date_start': value }, context=context)
1717
1718     _columns = {
1719         'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1720                             string='Duration', store=True, type='datetime'),
1721         'duration': fields.integer('Duration'),
1722     }
1723
1724     __attribute__ = {}
1725
1726
1727 calendar_todo()
1728
1729 class ir_attachment(osv.osv):
1730     _name = 'ir.attachment'
1731     _inherit = 'ir.attachment'
1732
1733     def search_count(self, cr, user, args, context=None):
1734         """
1735         @param self: The object pointer
1736         @param cr: the current row, from the database cursor,
1737         @param user: the current user’s ID for security checks,
1738         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1739         @param context: A standard dictionary for contextual values
1740         """
1741
1742         args1 = []
1743         for arg in args:
1744             args1.append(map(lambda x:str(x).split('-')[0], arg))
1745         return super(ir_attachment, self).search_count(cr, user, args1, context)
1746         
1747         
1748     
1749     def create(self, cr, uid, vals, context=None):
1750         if context:
1751             id = context.get('default_res_id', False)
1752             context.update({'default_res_id' : base_calendar_id2real_id(id)})
1753         return super(ir_attachment, self).create(cr, uid, vals, context=context)
1754
1755     def search(self, cr, uid, args, offset=0, limit=None, order=None,
1756             context=None, count=False):
1757         """
1758         @param self: The object pointer
1759         @param cr: the current row, from the database cursor,
1760         @param uid: the current user’s ID for security checks,
1761         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
1762         @param offset: The Number of Results to pass,
1763         @param limit: The Number of Results to Return,
1764         @param context: A standard dictionary for contextual values
1765         """
1766
1767         new_args = args
1768         for i, arg in enumerate(new_args):
1769             if arg[0] == 'res_id':
1770                 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1771
1772         return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1773                             limit=limit, order=order, context=context, count=False)
1774 ir_attachment()
1775
1776 class ir_values(osv.osv):
1777     _inherit = 'ir.values'
1778
1779     def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1780             isobject=False, meta=False, preserve_user=False, company=False):
1781         """
1782         Set IR Values
1783         @param self: The object pointer
1784         @param cr: the current row, from the database cursor,
1785         @param uid: the current user’s ID for security checks,
1786         @param model: Get The Model
1787         """
1788
1789         new_model = []
1790         for data in models:
1791             if type(data) in (list, tuple):
1792                 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1793             else:
1794                 new_model.append(data)
1795         return super(ir_values, self).set(cr, uid, key, key2, name, new_model, \
1796                     value, replace, isobject, meta, preserve_user, company)
1797
1798     def get(self, cr, uid, key, key2, models, meta=False, context=None, \
1799              res_id_req=False, without_user=True, key2_req=True):
1800         """
1801         Get IR Values
1802         @param self: The object pointer
1803         @param cr: the current row, from the database cursor,
1804         @param uid: the current user’s ID for security checks,
1805         @param model: Get The Model
1806         """
1807         if context is None:
1808             context = {}
1809         new_model = []
1810         for data in models:
1811             if type(data) in (list, tuple):
1812                 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1813             else:
1814                 new_model.append(data)
1815         return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1816                          meta, context, res_id_req, without_user, key2_req)
1817
1818 ir_values()
1819
1820 class ir_model(osv.osv):
1821
1822     _inherit = 'ir.model'
1823
1824     def read(self, cr, uid, ids, fields=None, context=None,
1825             load='_classic_read'):
1826         """
1827         Overrides orm read method.
1828         @param self: The object pointer
1829         @param cr: the current row, from the database cursor,
1830         @param uid: the current user’s ID for security checks,
1831         @param ids: List of IR Model’s IDs.
1832         @param context: A standard dictionary for contextual values
1833         """
1834         new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
1835         if context is None:
1836             context = {}
1837         data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \
1838                         context=context, load=load)
1839         if data:
1840             for val in data:
1841                 val['id'] = base_calendar_id2real_id(val['id'])
1842         return isinstance(ids, (str, int, long)) and data[0] or data
1843
1844 ir_model()
1845
1846 class virtual_report_spool(web_services.report_spool):
1847
1848     def exp_report(self, db, uid, object, ids, datas=None, context=None):
1849         """
1850         Export Report
1851         @param self: The object pointer
1852         @param db: get the current database,
1853         @param uid: the current user’s ID for security checks,
1854         @param context: A standard dictionary for contextual values
1855         """
1856
1857         if object == 'printscreen.list':
1858             return super(virtual_report_spool, self).exp_report(db, uid, \
1859                             object, ids, datas, context)
1860         new_ids = []
1861         for id in ids:
1862             new_ids.append(base_calendar_id2real_id(id))
1863         if datas.get('id', False):
1864             datas['id'] = base_calendar_id2real_id(datas['id'])
1865         return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1866
1867 virtual_report_spool()
1868
1869 class res_users(osv.osv):
1870     _inherit = 'res.users'
1871
1872     def _get_user_avail(self, cr, uid, ids, context=None):
1873         """
1874         Get User Availability
1875         @param self: The object pointer
1876         @param cr: the current row, from the database cursor,
1877         @param uid: the current user’s ID for security checks,
1878         @param ids: List of res user’s IDs.
1879         @param context: A standard dictionary for contextual values
1880         """
1881
1882         current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1883         res = {}
1884         attendee_obj = self.pool.get('calendar.attendee')
1885         attendee_ids = attendee_obj.search(cr, uid, [
1886                     ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1887                     ('state', '=', 'accepted'), ('user_id', 'in', ids)
1888                     ])
1889
1890         for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1891             user_id = attendee_data['user_id']
1892             status = 'busy'
1893             res.update({user_id:status})
1894
1895         #TOCHECK: Delegated Event
1896         for user_id in ids:
1897             if user_id not in res:
1898                 res[user_id] = 'free'
1899
1900         return res
1901
1902     def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1903         """
1904         Get User Availability Function
1905         @param self: The object pointer
1906         @param cr: the current row, from the database cursor,
1907         @param uid: the current user’s ID for security checks,
1908         @param ids: List of res user’s IDs.
1909         @param context: A standard dictionary for contextual values
1910         """
1911
1912         return self._get_user_avail(cr, uid, ids, context=context)
1913
1914     _columns = {
1915             'availability': fields.function(_get_user_avail_fun, type='selection', \
1916                     selection=[('free', 'Free'), ('busy', 'Busy')], \
1917                     string='Free/Busy', method=True),
1918     }
1919
1920 res_users()
1921
1922
1923 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: