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