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