[IMP]: caldav: Changed timezone on Changing user
[odoo/odoo.git] / addons / caldav / caldav.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 base_calendar import base_calendar
23 from datetime import datetime, timedelta
24 from datetime import datetime, timedelta
25 from dateutil import parser
26 from osv import fields, osv
27 from service import web_services
28 from tools.translate import _
29 import base64
30 import pooler
31 import pytz
32 import re
33 import time
34 import tools
35
36 months = {
37         1:"January", 2:"February", 3:"March", 4:"April", \
38         5:"May", 6:"June", 7:"July", 8:"August", 9:"September", \
39         10:"October", 11:"November", 12:"December"}
40
41 def caldav_id2real_id(caldav_id=None, with_date=False):
42     if caldav_id and isinstance(caldav_id, (str, unicode)):
43         res = caldav_id.split('-')
44         if len(res) >= 2:
45             real_id = res[0]
46             if with_date:
47                 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
48                                  time.strptime(res[1], "%Y%m%d%H%M%S"))
49                 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
50                 end = start + timedelta(hours=with_date)
51                 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
52             return int(real_id)
53     return caldav_id and int(caldav_id) or caldav_id
54
55 def real_id2caldav_id(real_id, recurrent_date):
56     if real_id and recurrent_date:
57         recurrent_date = time.strftime("%Y%m%d%H%M%S", \
58                          time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
59         return '%d-%s' % (real_id, recurrent_date)
60     return real_id
61
62
63 def _links_get(self, cr, uid, context={}):
64     obj = self.pool.get('res.request.link')
65     ids = obj.search(cr, uid, [])
66     res = obj.read(cr, uid, ids, ['object', 'name'], context)
67     return [(r['object'], r['name']) for r in res]
68
69 html_invitation = """
70 <html>
71 <head>
72 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
73 <title>%(name)s</title>
74 </head>
75 <body>
76 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
77     style="font-family: Arial, Sans-serif; font-size: 14">
78     <tr>
79         <td width="100%%">Hello,</td>
80     </tr>
81     <tr>
82         <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
83     </tr>
84     <tr>
85         <td width="100%%">Below are the details of event:</td>
86     </tr>
87 </table>
88
89 <table cellspacing="0" cellpadding="5" border="0" summary=""
90     style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
91     <tr valign="center" align="center">
92         <td bgcolor="DFDFDF">
93         <h3>%(name)s</h3>
94         </td>
95     </tr>
96     <tr>
97         <td>
98         <table cellpadding="8" cellspacing="0" border="0"
99             style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
100             width="90%%">
101             <tr>
102                 <td width="21%%">
103                 <div><b>Start Date</b></div>
104                 </td>
105                 <td><b>:</b></td>
106                 <td>%(start_date)s</td>
107                 <td width="15%%">
108                 <div><b>End Date</b></div>
109                 </td>
110                 <td><b>:</b></td>
111                 <td width="25%%">%(end_date)s</td>
112             </tr>
113             <tr valign="top">
114                 <td><b>Description</b></td>
115                 <td><b>:</b></td>
116                 <td colspan="3">%(description)s</td>
117             </tr>
118             <tr valign="top">
119                 <td>
120                 <div><b>Location</b></div>
121                 </td>
122                 <td><b>:</b></td>
123                 <td colspan="3">%(location)s</td>
124             </tr>
125             <tr valign="top">
126                 <td>
127                 <div><b>Event Attendees</b></div>
128                 </td>
129                 <td><b>:</b></td>
130                 <td colspan="3">
131                 <div>
132                 <div>%(attendees)s</div>
133                 </div>
134                 </td>
135             </tr>
136             <tr valign="top">
137                 <td><b>Are you coming?</b></td>
138                 <td><b>:</b></td>
139                 <td colspan="3">
140                 <UL>
141                     <LI>YES</LI>
142                     <LI>NO</LI>
143                     <LI>MAYBE</LI>
144                 </UL>
145                 </td>
146             </tr>
147         </table>
148         </td>
149     </tr>
150 </table>
151 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
152     style="font-family: Arial, Sans-serif; font-size: 14">
153     <tr>
154         <td width="100%%"><b>Note:</b> If you are interested please reply this
155         mail and keep only your response from options <i>YES, NO</i>
156         and <i>MAYBE</i>.</td>
157     </tr>
158     <tr>
159         <td width="100%%">From:</td>
160     </tr>
161     <tr>
162         <td width="100%%">%(user)s</td>
163     </tr>
164     <tr valign="top">
165         <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
166     </tr>
167     <tr>
168         <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
169     </tr>
170 </table>
171 </body>
172 </html>
173 """
174
175 class invite_attendee_wizard(osv.osv_memory):
176     _name = "caldav.invite.attendee"
177     _description = "Invite Attendees"
178
179     _columns = {
180         'type': fields.selection([('internal', 'Internal User'), \
181               ('external', 'External Email'), \
182               ('partner', 'Partner Contacts')], 'Type', required=True), 
183         'user_ids': fields.many2many('res.users', 'invite_user_rel', 
184                                   'invite_id', 'user_id', 'Users'), 
185         'partner_id': fields.many2one('res.partner', 'Partner'), 
186         'email': fields.char('Email', size=124), 
187         'contact_ids': fields.many2many('res.partner.address', 'invite_contact_rel', 
188                                   'invite_id', 'contact_id', 'Contacts'), 
189         'send_mail': fields.boolean('Send mail?', help='Check this if you want\
190  to send an Email to Invited Person')
191               }
192
193     def do_invite(self, cr, uid, ids, context={}):
194         datas = self.read(cr, uid, ids)[0]
195         model = False
196         model_field = False
197         if not context or not context.get('model'):
198             return {}
199         else:
200             model = context.get('model')
201         model_field = context.get('attendee_field', False)
202         obj = self.pool.get(model)
203         res_obj = obj.browse(cr, uid, context['active_id'])
204         type = datas.get('type')
205         att_obj = self.pool.get('calendar.attendee')
206         vals = {}
207         mail_to = []
208         if not model == 'calendar.attendee':
209             vals = {'ref': '%s,%s' % (model, caldav_id2real_id(context['active_id']))}
210
211         if type == 'internal':
212             user_obj = self.pool.get('res.users')
213             for user_id in datas.get('user_ids', []):
214                 user = user_obj.browse(cr, uid, user_id)
215                 if not user.address_id.email:
216                     raise osv.except_osv(_('Error!'), \
217                                     ("User does not have an email Address"))
218                 vals.update({'user_id': user_id, 
219                                      'email': user.address_id.email})
220                 mail_to.append(user.address_id.email)
221                 
222         elif  type == 'external' and datas.get('email'):
223             vals.update({'email': datas['email']})
224             mail_to.append(datas['email'])
225         elif  type == 'partner':
226             add_obj = self.pool.get('res.partner.address')
227             for contact in  add_obj.browse(cr, uid, datas['contact_ids']):
228                 if not contact.email:
229                     raise osv.except_osv(_('Error!'), \
230                                     ("Partner does not have an email Address"))
231                 vals.update({
232                              'partner_address_id': contact.id, 
233                              'email': contact.email})
234                 mail_to.append(contact.email)
235
236         if model == 'calendar.attendee':
237             att = att_obj.browse(cr, uid, context['active_id'])
238             vals.update({
239                 'parent_ids' : [(4, att.id)],
240                 'ref': att.ref
241             })
242         att_id = att_obj.create(cr, uid, vals)
243         if model_field:
244             obj.write(cr, uid, res_obj.id, {model_field: [(4, att_id)], 
245                                             'state': 'delegated'})
246         
247         if datas.get('send_mail'):
248             att_obj._send_mail(cr, uid, [att_id], mail_to, \
249                    email_from=tools.config.get('email_from', False))
250                 
251         return {}
252
253
254     def onchange_partner_id(self, cr, uid, ids, partner_id, *args, **argv):
255         if not partner_id:
256             return {'value': {'contact_ids': []}}
257         cr.execute('select id from res_partner_address \
258                          where partner_id=%s' % (partner_id))
259         contacts = map(lambda x: x[0], cr.fetchall())
260         if not contacts:
261             raise osv.except_osv(_('Error!'), \
262                                 ("Partner does not have any Contacts"))
263
264         return {'value': {'contact_ids': contacts}}
265
266 invite_attendee_wizard()
267
268 class calendar_attendee(osv.osv):
269     _name = 'calendar.attendee'
270     _description = 'Attendee information'
271     _rec_name = 'cutype'
272
273     __attribute__ = {}
274
275     def _get_address(self, name=None, email=None):
276         if name and email:
277             name += ':'
278         return (name or '') + (email and ('MAILTO:' + email) or '')
279
280     def _compute_data(self, cr, uid, ids, name, arg, context):
281         name = name[0]
282         result = {}
283
284         for attdata in self.browse(cr, uid, ids, context=context):
285             id = attdata.id
286             result[id] = {}
287             if name == 'sent_by':
288                 if not attdata.sent_by_uid:
289                     result[id][name] = ''
290                     continue
291                 else:
292                     result[id][name] =  self._get_address(attdata.sent_by_uid.name, \
293                                         attdata.sent_by_uid.address_id.email)
294             if name == 'cn':
295                 if attdata.user_id:
296                     result[id][name] = self._get_address(attdata.user_id.name, attdata.email)
297                 elif attdata.partner_address_id:
298                     result[id][name] = self._get_address(attdata.partner_id.name, attdata.email)
299                 else:
300                     result[id][name] = self._get_address(None, attdata.email)
301             if name == 'delegated_to':
302                 todata = []
303                 for parent in attdata.parent_ids:
304                     todata.append('MAILTO:' + parent.email)
305                 result[id][name] = ', '.join(todata)
306             if name == 'delegated_from':
307                 fromdata = []
308                 for child in attdata.child_ids:
309                     fromdata.append('MAILTO:' + child.email)
310                 result[id][name] = ', '.join(fromdata)
311             if name == 'event_date':
312                 if attdata.ref:
313                     model, res_id = tuple(attdata.ref.split(','))
314                     model_obj = self.pool.get(model)
315                     obj = model_obj.read(cr, uid, res_id, ['date'])[0]
316                     result[id][name] = obj.get('date')
317                 else:
318                     result[id][name] = None
319             if name == 'event_end_date':
320                 if attdata.ref:
321                     model, res_id = tuple(attdata.ref.split(','))
322                     model_obj = self.pool.get(model)
323                     obj = model_obj.read(cr, uid, res_id, ['date_deadline'])[0]
324                     result[id][name] = obj.get('date_deadline')
325                 else:
326                     result[id][name] = None
327             if name == 'sent_by_uid':
328                 if attdata.ref:
329                     model, res_id = tuple(attdata.ref.split(','))
330                     model_obj = self.pool.get(model)
331                     obj = model_obj.read(cr, uid, res_id, ['user_id'])[0]
332                     result[id][name] = obj.get('user_id')
333                 else:
334                     result[id][name] = uid
335             if name == 'language':
336                 user_obj = self.pool.get('res.users')
337                 lang = user_obj.read(cr, uid, uid, ['context_lang'])['context_lang']
338                 result[id][name] = lang.replace('_', '-')
339         return result
340
341     def _links_get(self, cr, uid, context={}):
342         obj = self.pool.get('res.request.link')
343         ids = obj.search(cr, uid, [])
344         res = obj.read(cr, uid, ids, ['object', 'name'], context)
345         return [(r['object'], r['name']) for r in res]
346
347     def _lang_get(self, cr, uid, context={}):
348         obj = self.pool.get('res.lang')
349         ids = obj.search(cr, uid, [])
350         res = obj.read(cr, uid, ids, ['code', 'name'], context)
351         res = [((r['code']).replace('_', '-'), r['name']) for r in res]
352         return res
353
354     _columns = {
355         'cutype': fields.selection([('individual', 'Individual'), \
356                     ('group', 'Group'), ('resource', 'Resource'), \
357                     ('room', 'Room'), ('unknown', 'Unknown') ], \
358                     'User Type', help="Specify the type of calendar user"), 
359         'member': fields.char('Member', size=124, 
360                     help="Indicate the groups that the attendee belongs to"), 
361         'role': fields.selection([('req-participant', 'Participation required'), \
362                     ('chair', 'Chair Person'), \
363                     ('opt-participant', 'Optional Participation'), \
364                     ('non-participant', 'For information Purpose')], 'Role', \
365                     help='Participation role for the calendar user'), 
366         'state': fields.selection([('tentative', 'Tentative'), 
367                         ('needs-action', 'Needs Action'), 
368                         ('accepted', 'Accepted'), 
369                         ('declined', 'Declined'), 
370                         ('delegated', 'Delegated')], 'State', readonly=True, 
371                         help="Status of the attendee's participation"), 
372         'rsvp':  fields.boolean('Required Reply?', 
373                     help="Indicats whether the favor of a reply is requested"), 
374         'delegated_to': fields.function(_compute_data, method=True, \
375                 string='Delegated To', type="char", size=124, store=True, \
376                 multi='delegated_to', help="The users that the original \
377 request was delegated to"),         
378         'delegated_from': fields.function(_compute_data, method=True, string=\
379             'Delegated From', type="char", store=True, size=124, multi='delegated_from'),        
380         'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', 'attendee_id', 'parent_id', 'Delegrated From'),
381         'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', 'attendee_id', 'child_id', 'Delegrated To'),  
382         'sent_by': fields.function(_compute_data, method=True, string='Sent By', type="char", multi='sent_by', store=True, size=124, help="Specify the user that is acting on behalf of the calendar user"), 
383         'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', type="many2one", relation="res.users", multi='sent_by_uid'), 
384         'cn': fields.function(_compute_data, method=True, string='Common name', type="char", size=124, multi='cn', store=True), 
385         'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."), 
386         'language':  fields.function(_compute_data, method=True, string='Language', type="selection", selection=_lang_get, multi='language', store=True, help="To specify the language for text values in a property or property parameter."), 
387         'user_id': fields.many2one('res.users', 'User'), 
388         'partner_address_id': fields.many2one('res.partner.address', 'Contact'), 
389         'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'), 
390         'email': fields.char('Email', size=124, required=True), 
391         'event_date': fields.function(_compute_data, method=True, string='Event Date', type="datetime", multi='event_date'), 
392         'event_end_date': fields.function(_compute_data, method=True, string='Event End Date', type="datetime", multi='event_end_date'), 
393         'ref': fields.reference('Document Ref', selection=_links_get, size=128), 
394         'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), 
395      }
396     _defaults = {
397         'state':  lambda *x: 'needs-action', 
398     }
399     
400     response_re = response_re = re.compile("Are you coming\?.*\n*.*(YES|NO|MAYBE).*", re.UNICODE)
401     
402     def msg_new(self, cr, uid, msg):        
403         return False
404         
405     def msg_act_get(self, msg):
406         mailgate_obj = self.pool.get('mail.gateway')
407         body = mailgate_obj.msg_body_get(msg)
408         actions = {}
409         res = self.response_re.findall(body['body'])
410         if res:
411                 actions['state'] = res[0]
412         return actions
413
414     def msg_update(self, cr, uid, ids, msg, data={}, default_act='None'):
415         msg_actions = self.msg_act_get(msg)
416         if msg_actions.get('state'):
417             if msg_actions['state'] in ['YES', 'NO', 'MAYBE']:
418                 mapping = {'YES': 'accepted', 'NO': 'declined', 'MAYBE': 'tentative'}
419                 status = mapping[msg_actions['state']]
420                 print 'Got response for invitation id: %s as %s'  % (ids, status)
421                 self.write(cr, uid, ids, {'state': status})
422         return True
423
424     def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context={}):
425         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
426         for att in self.browse(cr, uid, ids, context=context):
427             sign = att.sent_by_uid and att.sent_by_uid.signature or ''
428             sign = '<br>'.join(sign and sign.split('\n') or [])
429             model, res_id = tuple(att.ref.split(','))            
430             res_obj = self.pool.get(model).browse(cr, uid, res_id)
431             if res_obj and len(res_obj):
432                 res_obj = res_obj[0]
433             sub = '[%s Invitation][%d] %s'  % (company, att.id, res_obj.name)
434             att_infos = []
435             other_invitaion_ids = self.search(cr, uid, [('ref','=',att.ref)])
436             for att2 in self.browse(cr, uid, other_invitaion_ids):
437                 att_infos.append(((att2.user_id and att2.user_id.name) or \
438                              (att2.partner_id and att2.partner_id.name) or \
439                                 att2.email) +  ' - Status: ' + att2.state.title())
440             body_vals = {'name': res_obj.name, 
441                         'start_date': res_obj.date, 
442                         'end_date': res_obj.date_deadline or None, 
443                         'description': res_obj.description or '-', 
444                         'location': res_obj.location or '-', 
445                         'attendees': '<br>'.join(att_infos), 
446                         'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User', 
447                         'sign': sign, 
448                         'company': company
449             }
450             body = html_invitation % body_vals
451             if mail_to and email_from:
452                 tools.email_send(
453                         email_from, 
454                         mail_to, 
455                         sub, 
456                         body, 
457                         subtype='html', 
458                         reply_to=email_from
459                     ) 
460             return True
461     def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
462         if not user_id:
463             return {'value': {'email': ''}}
464         usr_obj = self.pool.get('res.users')
465         user = usr_obj.browse(cr, uid, user_id, *args)
466         return {'value': {'email': user.address_id.email, 'availability':user.availability}}
467
468     def do_tentative(self, cr, uid, ids, context=None, *args):
469         self.write(cr, uid, ids, {'state': 'tentative'}, context)
470
471     def do_accept(self, cr, uid, ids, context=None, *args):
472         self.write(cr, uid, ids, {'state': 'accepted'}, context)
473
474     def do_decline(self, cr, uid, ids, context=None, *args):
475         self.write(cr, uid, ids, {'state': 'declined'}, context)
476
477     def create(self, cr, uid, vals, context={}):
478         if not vals.get("email") and vals.get("cn"):
479             cnval = vals.get("cn").split(':')
480             email =  filter(lambda x:x.__contains__('@'), cnval)
481             vals['email'] = email[0]
482             vals['cn'] = vals.get("cn")
483         res = super(calendar_attendee, self).create(cr, uid, vals, context)
484         return res
485     
486 calendar_attendee()
487
488 class res_alarm(osv.osv):
489     _name = 'res.alarm'
490     _description = 'Basic Alarm Information'
491     _columns = {
492         'name':fields.char('Name', size=256, required=True), 
493         'trigger_occurs': fields.selection([('before', 'Before'), ('after', 'After')], \
494                                         'Triggers', required=True), 
495         'trigger_interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), \
496                 ('days', 'Days')], 'Interval', required=True), 
497         'trigger_duration':  fields.integer('Duration', required=True), 
498         'trigger_related':  fields.selection([('start', 'The event starts'), ('end', \
499                                        'The event ends')], 'Related to', required=True), 
500         'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
501 are both optional, but if one occurs, so MUST the other"""), 
502         'repeat': fields.integer('Repeat'), 
503         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the event alarm information without removing it."), 
504
505
506     }
507     _defaults = {
508         'trigger_interval':  lambda *x: 'minutes', 
509         'trigger_duration': lambda *x: 5, 
510         'trigger_occurs': lambda *x: 'before', 
511         'trigger_related': lambda *x: 'start', 
512         'active': lambda *x: 1, 
513     }
514
515     def do_alarm_create(self, cr, uid, ids, model, date, context={}):
516         alarm_obj = self.pool.get('calendar.alarm')
517         ir_obj = self.pool.get('ir.model')
518         model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
519         
520         model_obj = self.pool.get(model)
521         for data in model_obj.browse(cr, uid, ids):
522             basic_alarm = data.alarm_id
523             if not context.get('alarm_id'):
524                 self.do_alarm_unlink(cr, uid, [data.id], model)
525                 return True
526             self.do_alarm_unlink(cr, uid, [data.id], model)
527             if basic_alarm:
528                 vals = {
529                     'action': 'display', 
530                     'description': data.description, 
531                     'name': data.name, 
532                     'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))], 
533                     'trigger_related': basic_alarm.trigger_related, 
534                     'trigger_duration': basic_alarm.trigger_duration, 
535                     'trigger_occurs': basic_alarm.trigger_occurs, 
536                     'trigger_interval': basic_alarm.trigger_interval, 
537                     'duration': basic_alarm.duration, 
538                     'repeat': basic_alarm.repeat, 
539                     'state': 'run', 
540                     'event_date': data[date], 
541                     'res_id': data.id, 
542                     'model_id': model_id, 
543                     'user_id': uid
544                  }
545                 alarm_id = alarm_obj.create(cr, uid, vals)
546                 cr.execute('Update %s set caldav_alarm_id=%s, alarm_id=%s \
547                                         where id=%s' % (model_obj._table, \
548                                         alarm_id, basic_alarm.id, data.id))
549         cr.commit()
550         return True
551
552     def do_alarm_unlink(self, cr, uid, ids, model, context={}):
553         alarm_obj = self.pool.get('calendar.alarm')
554         ir_obj = self.pool.get('ir.model')
555         model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
556         model_obj = self.pool.get(model)
557         for datas in model_obj.browse(cr, uid, ids):
558             alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
559             if alarm_ids:
560                 alarm_obj.unlink(cr, uid, alarm_ids)
561                 cr.execute('Update %s set caldav_alarm_id=NULL, alarm_id=NULL\
562                              where id=%s' % (model_obj._table, datas.id))
563         cr.commit()
564         return True
565
566 res_alarm()
567
568 class calendar_alarm(osv.osv):
569     _name = 'calendar.alarm'
570     _description = 'Event alarm information'
571     _inherit = 'res.alarm'
572     __attribute__ = {}
573
574     _columns = {
575             'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'), 
576             'name': fields.char('Summary', size=124, help="""Contains the text to be used as the message subject for email
577 or contains the text to be used for display"""), 
578             'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
579                     ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
580                     required=True, help="Defines the action to be invoked when an alarm is triggered"), 
581             'description': fields.text('Description', help='Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property'), 
582             'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
583                                           'alarm_id', 'attendee_id', 'Attendees', readonly=True), 
584             'attach': fields.binary('Attachment', help="""* Points to a sound resource, which is rendered when the alarm is triggered for audio,
585 * File which is intended to be sent as message attachments for email,
586 * Points to a procedure resource, which is invoked when the alarm is triggered for procedure."""), 
587             'res_id': fields.integer('Resource ID'), 
588             'model_id': fields.many2one('ir.model', 'Model'), 
589             'user_id': fields.many2one('res.users', 'Owner'), 
590             'event_date': fields.datetime('Event Date'), 
591             'event_end_date': fields.datetime('Event End Date'), 
592             'trigger_date': fields.datetime('Trigger Date', readonly="True"), 
593             'state':fields.selection([
594                         ('draft', 'Draft'), 
595                         ('run', 'Run'), 
596                         ('stop', 'Stop'), 
597                         ('done', 'Done'), 
598                     ], 'State', select=True, readonly=True), 
599      }
600
601     _defaults = {
602         'action':  lambda *x: 'email', 
603         'state': lambda *x: 'run', 
604      }
605
606     def create(self, cr, uid, vals, context={}):
607         event_date = vals.get('event_date', False)
608         if event_date:
609             dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
610             if vals['trigger_interval'] == 'days':
611                 delta = timedelta(days=vals['trigger_duration'])
612             if vals['trigger_interval'] == 'hours':
613                 delta = timedelta(hours=vals['trigger_duration'])
614             if vals['trigger_interval'] == 'minutes':
615                 delta = timedelta(minutes=vals['trigger_duration'])
616             trigger_date =  dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
617             vals['trigger_date'] = trigger_date
618         res = super(calendar_alarm, self).create(cr, uid, vals, context)
619         return res
620
621     def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False,\
622                        context=None):
623         if not context:
624             context = {}
625         current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
626         cr.execute("select alarm.id as id \
627                     from calendar_alarm alarm \
628                     where alarm.state = %s and alarm.trigger_date <= %s", ('run', current_datetime))
629         res = cr.dictfetchall()
630         alarm_ids = map(lambda x: x['id'], res)
631         attendee_obj = self.pool.get('calendar.attendee')
632         request_obj = self.pool.get('res.request')
633         mail_to = []
634         for alarm in self.browse(cr, uid, alarm_ids):
635             if alarm.action == 'display':
636                 value = {
637                    'name': alarm.name, 
638                    'act_from': alarm.user_id.id, 
639                    'act_to': alarm.user_id.id, 
640                    'body': alarm.description, 
641                    'trigger_date': alarm.trigger_date, 
642                    'ref_doc1':  '%s,%s'  % (alarm.model_id.model, alarm.res_id)
643                 }
644                 request_id = request_obj.create(cr, uid, value)
645                 request_ids = [request_id]
646                 for attendee in alarm.attendee_ids:
647                     value['act_to'] = attendee.user_id.id
648                     request_id = request_obj.create(cr, uid, value)
649                     request_ids.append(request_id)
650                 request_obj.request_send(cr, uid, request_ids)
651
652             if alarm.action == 'email':
653                 sub = '[Openobject Remainder] %s'  % (alarm.name)
654                 body = """
655                 Name: %s
656                 Date: %s
657                 Description: %s
658
659                 From:
660                       %s
661                       %s
662
663                 """  % (alarm.name, alarm.trigger_date, alarm.description, \
664                     alarm.user_id.name, alarm.user_id.signature)
665                 mail_to = [alarm.user_id.address_id.email]
666                 for att in alarm.attendee_ids:
667                     mail_to.append(att.user_id.address_id.email)
668
669                 tools.email_send(
670                     tools.config.get('email_from', False), 
671                     mail_to, 
672                     sub, 
673                     body
674                 )
675             self.write(cr, uid, [alarm.id], {'state':'done'})
676         return True
677
678 calendar_alarm()
679
680
681 class calendar_event(osv.osv):
682     _name = "calendar.event"
683     _description = "Calendar Event"
684     __attribute__ = {}
685     
686     def _tz_get(self, cr, uid, context={}):
687         return [(x.lower(), x) for x in pytz.all_timezones]
688
689     def onchange_rrule_type(self, cr, uid, ids, rtype, *args, **argv):
690         if rtype == 'none' or not rtype:
691             return {'value': {'rrule': ''}}
692         if rtype == 'custom':
693             return {}
694         rrule = self.pool.get('calendar.custom.rrule')
695         rrulestr = rrule.compute_rule_string(cr, uid, {'freq': rtype.upper(), \
696                                  'interval': 1})
697         return {'value': {'rrule': rrulestr}}
698     
699     def _get_duration(self, cr, uid, ids, name, arg, context):
700         res = {}
701         for event in self.browse(cr, uid, ids, context=context):
702             start = datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")
703             res[event.id] = 0
704             if event.date_deadline:
705                 end = datetime.strptime(event.date_deadline[:19], "%Y-%m-%d %H:%M:%S")
706                 diff = end - start
707                 duration =  float(diff.days)* 24 + (float(diff.seconds) / 3600)
708                 res[event.id] = round(duration, 2)
709         return res
710
711     def _set_duration(self, cr, uid, id, name, value, arg, context):
712         event = self.browse(cr, uid, id, context=context)
713         start = datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")
714         end = start + timedelta(hours=value)
715         cr.execute("UPDATE %s set date_deadline='%s' \
716                         where id=%s"% (self._table, end.strftime("%Y-%m-%d %H:%M:%S"), id))
717         return True
718     
719     _columns = {
720         'id': fields.integer('ID'), 
721         'sequence': fields.integer('Sequence'), 
722         'name': fields.char('Description', size=64, required=True), 
723         'date': fields.datetime('Date'), 
724         'date_deadline': fields.datetime('Deadline'), 
725         'create_date': fields.datetime('Created', readonly=True), 
726         'duration': fields.function(_get_duration, method=True, \
727                                     fnct_inv=_set_duration, string='Duration'), 
728         'description': fields.text('Your action'), 
729         'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
730                  ('confidential', 'Confidential')], 'Mark as'), 
731         'location': fields.char('Location', size=264, help="Location of Event"), 
732         'show_as': fields.selection([('free', 'Free'), \
733                                   ('busy', 'Busy')], 
734                                    'Show as'), 
735         'caldav_url': fields.char('Caldav URL', size=264), 
736         'exdate': fields.text('Exception Date/Times', help="This property \
737 defines the list of date/time exceptions for arecurring calendar component."), 
738         'exrule': fields.char('Exception Rule', size=352, help="defines a \
739 rule or repeating pattern for anexception to a recurrence set"), 
740         'rrule': fields.char('Recurrent Rule', size=124), 
741         'rrule_type': fields.selection([('none', 'None'), ('daily', 'Daily'), \
742                             ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
743                             ('yearly', 'Yearly'), ('custom', 'Custom')], 'Recurrency'), 
744         'alarm_id': fields.many2one('res.alarm', 'Alarm'), 
745         'caldav_alarm_id': fields.many2one('calendar.alarm', 'Alarm'), 
746         'recurrent_uid': fields.integer('Recurrent ID'), 
747         'recurrent_id': fields.datetime('Recurrent ID date'), 
748         'vtimezone': fields.selection(_tz_get, 'Timezone', size=64), 
749         'user_id': fields.many2one('res.users', 'Responsible'), 
750     }
751     
752     _defaults = {
753          'class': lambda *a: 'public', 
754          'show_as': lambda *a: 'busy', 
755     }
756     
757     def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
758         if not user_id:
759             return {'value': {'vtimezone': ''}}
760         value = {}
761         cr.execute('select context_tz from res_users where id=%s' % (user_id))
762         timezone = cr.fetchone()
763         if timezone:
764             value.update({'vtimezone': timezone[0].lower()})
765         return {'value': value}
766
767     def export_cal(self, cr, uid, ids, context={}):
768         ids = map(lambda x: caldav_id2real_id(x), ids)
769         event_data = self.read(cr, uid, ids)
770         event_obj = self.pool.get('basic.calendar.event')
771         ical = event_obj.export_cal(cr, uid, event_data, context={'model': self._name})
772         return ical.serialize()
773
774     def import_cal(self, cr, uid, data, data_id=None, context={}):
775         event_obj = self.pool.get('basic.calendar.event')
776         vals = event_obj.import_cal(cr, uid, data, context=context)
777         return self.check_import(cr, uid, vals, context=context)
778     
779     def check_import(self, cr, uid, vals, context={}):
780         ids = []
781         for val in vals:
782             exists, r_id = base_calendar.uid2openobjectid(cr, val['id'], \
783                                     self._name, val.get('recurrent_id'))
784             if val.has_key('create_date'): val.pop('create_date')
785             val['caldav_url'] = context.get('url') or ''
786             val.pop('id')
787             if exists and r_id:
788                 val.update({'recurrent_uid': exists})
789                 self.write(cr, uid, [r_id], val)
790                 ids.append(r_id)
791             elif exists:
792                 self.write(cr, uid, [exists], val)
793                 ids.append(exists)
794             else:
795                 event_id = self.create(cr, uid, val)
796                 ids.append(event_id)
797         return ids
798         
799     def modify_this(self, cr, uid, ids, defaults, context=None, *args):
800         datas = self.read(cr, uid, ids[0], context=context)
801         date = datas.get('date')
802         defaults.update({
803                'recurrent_uid': caldav_id2real_id(datas['id']), 
804                'recurrent_id': defaults.get('date'), 
805                'rrule_type': 'none', 
806                'rrule': ''
807                     })
808         new_id = self.copy(cr, uid, ids[0], default=defaults, context=context)
809         return new_id
810
811     def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
812         if not limit:
813             limit = 100
814         if isinstance(select, (str, int, long)):
815             ids = [select]
816         else:
817             ids = select
818         result = []
819         if ids and (base_start_date or base_until_date):
820             cr.execute("select m.id, m.rrule, m.date, m.date_deadline, \
821                             m.exdate  from "  + self._table + \
822                             " m where m.id in ("\
823                             + ','.join(map(lambda x: str(x), ids))+")")
824
825             count = 0
826             for data in cr.dictfetchall():
827                 start_date = base_start_date and datetime.strptime(base_start_date, "%Y-%m-%d") or False
828                 until_date = base_until_date and datetime.strptime(base_until_date, "%Y-%m-%d") or False
829                 if count > limit:
830                     break
831                 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
832                 if start_date and start_date <= event_date:
833                     start_date = event_date
834                 if not data['rrule']:
835                     if start_date and (event_date < start_date):
836                         continue
837                     if until_date and (event_date > until_date):
838                         continue
839                     idval = real_id2caldav_id(data['id'], data['date'])
840                     result.append(idval)
841                     count += 1
842                 else:
843                     exdate = data['exdate'] and data['exdate'].split(',') or []
844                     event_obj = self.pool.get('basic.calendar.event')
845                     rrule_str = data['rrule']
846                     new_rrule_str = []
847                     rrule_until_date = False
848                     is_until = False
849                     for rule in rrule_str.split(';'):
850                         name, value = rule.split('=')
851                         if name == "UNTIL":
852                             is_until = True
853                             value = parser.parse(value)
854                             rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
855                             if until_date and until_date >= rrule_until_date:
856                                 until_date = rrule_until_date
857                             if until_date:
858                                 value = until_date.strftime("%Y%m%d%H%M%S")
859                         new_rule = '%s=%s' % (name, value)
860                         new_rrule_str.append(new_rule)
861                     if not is_until and until_date:
862                         value = until_date.strftime("%Y%m%d%H%M%S")
863                         name = "UNTIL"
864                         new_rule = '%s=%s' % (name, value)
865                         new_rrule_str.append(new_rule)
866                     new_rrule_str = ';'.join(new_rrule_str)
867                     start_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
868                     rdates = event_obj.get_recurrent_dates(str(new_rrule_str), exdate, start_date)
869                     for rdate in rdates:
870                         r_date = datetime.strptime(rdate, "%Y-%m-%d %H:%M:%S")
871                         if start_date and r_date < start_date:
872                             continue
873                         if until_date and r_date > until_date:
874                             continue
875                         idval = real_id2caldav_id(data['id'], rdate)
876                         result.append(idval)
877                         count += 1
878         if result:
879             ids = result
880         if isinstance(select, (str, int, long)):
881             return ids and ids[0] or False
882         return ids
883
884     def search(self, cr, uid, args, offset=0, limit=100, order=None, 
885             context=None, count=False):
886         args_without_date = []
887         start_date = False
888         until_date = False
889         for arg in args:
890             if arg[0] not in ('date', unicode('date')):
891                 args_without_date.append(arg)
892             else:
893                 if arg[1] in ('>', '>='):
894                     start_date = arg[2]
895                 elif arg[1] in ('<', '<='):
896                     until_date = arg[2]
897         res = super(calendar_event, self).search(cr, uid, args_without_date, \
898                                  offset, limit, order, context, count)
899         return self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
900
901
902     def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
903         if not context:
904             context = {}
905         if isinstance(ids, (str, int, long)):
906             select = [ids]
907         else:
908             select = ids
909         new_ids = []
910         for id in select:
911             id = caldav_id2real_id(id)
912             if not id in new_ids:
913                 new_ids.append(id)
914         res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
915         if vals.has_key('alarm_id') or vals.has_key('caldav_alarm_id'):
916             alarm_obj = self.pool.get('res.alarm')
917             context.update({'alarm_id': vals.get('alarm_id')})
918             alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
919         return res
920
921     def browse(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
922         if isinstance(ids, (str, int, long)):
923             select = [ids]
924         else:
925             select = ids
926         select = map(lambda x: caldav_id2real_id(x), select)
927         res = super(calendar_event, self).browse(cr, uid, select, context, list_class, fields_process)
928         if isinstance(ids, (str, int, long)):
929             return res and res[0] or False
930         return res
931
932     def read(self, cr, uid, ids, fields=None, context={}, load='_classic_read'):
933         if isinstance(ids, (str, int, long)):
934             select = [ids]
935         else:
936             select = ids
937         select = map(lambda x: (x, caldav_id2real_id(x)), select)
938         result = []
939         if fields and 'date' not in fields:
940             fields.append('date')
941         for caldav_id, real_id in select:
942             res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, \
943                                               load=load)
944             ls = caldav_id2real_id(caldav_id, with_date=res.get('duration', 0))
945             if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
946                 res['date'] = ls[1]
947                 res['date_deadline'] = ls[2]
948             res['id'] = caldav_id
949
950             result.append(res)
951         if isinstance(ids, (str, int, long)):
952             return result and result[0] or False
953         return result
954
955     def copy(self, cr, uid, id, default=None, context={}):
956         res = super(calendar_event, self).copy(cr, uid, caldav_id2real_id(id), default, context)
957         alarm_obj = self.pool.get('res.alarm')
958         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
959         return res
960
961     def unlink(self, cr, uid, ids, context=None):
962         res = False
963         for id in ids:
964             ls = caldav_id2real_id(id)
965             if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
966                 date_new = ls[1]
967                 for record in self.read(cr, uid, [caldav_id2real_id(id)], \
968                                             ['date', 'rrule', 'exdate']):
969                     if record['rrule']:
970                         exdate = (record['exdate'] and (record['exdate'] + ',')  or '') + ''.join((re.compile('\d')).findall(date_new)) + 'Z'
971                         if record['date'] == date_new:
972                             res = self.write(cr, uid, [caldav_id2real_id(id)], {'exdate': exdate})
973                     else:
974                         ids = map(lambda x: caldav_id2real_id(x), ids)
975                         res = super(calendar_event, self).unlink(cr, uid, caldav_id2real_id(ids))
976                         alarm_obj = self.pool.get('res.alarm')
977                         alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
978             else:
979                 ids = map(lambda x: caldav_id2real_id(x), ids)
980                 res = super(calendar_event, self).unlink(cr, uid, ids)
981                 alarm_obj = self.pool.get('res.alarm')
982                 alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
983         return res
984
985     def create(self, cr, uid, vals, context={}):
986         res = super(calendar_event, self).create(cr, uid, vals, context)
987         alarm_obj = self.pool.get('res.alarm')
988         alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
989         return res
990
991 calendar_event()
992
993 class calendar_todo(osv.osv):
994     _name = "calendar.todo"
995     _inherit = "calendar.event"
996     _description = "Calendar Task"
997
998     def _get_date(self, cr, uid, ids, name, arg, context):
999         res = {}
1000         for event in self.browse(cr, uid, ids, context=context):
1001             res[event.id] = event.date_start
1002         return res
1003
1004     def _set_date(self, cr, uid, id, name, value, arg, context):
1005         event = self.browse(cr, uid, id, context=context)
1006         cr.execute("UPDATE %s set date_start='%s' where id=%s"  \
1007                            % (self._table, value, id))
1008         return True
1009
1010     _columns = {
1011         'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1012                                         string='Duration', store=True, type='datetime'), 
1013         'duration': fields.integer('Duration'), 
1014     }
1015     
1016     __attribute__ = {}
1017     
1018     def import_cal(self, cr, uid, data, data_id=None, context={}):
1019         todo_obj = self.pool.get('basic.calendar.todo')
1020         vals = todo_obj.import_cal(cr, uid, data, context=context)
1021         return self.check_import(cr, uid, vals, context=context)
1022     
1023     def check_import(self, cr, uid, vals, context={}):
1024         ids = []
1025         for val in vals:
1026             obj_tm = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.project_time_mode_id
1027             if not val.has_key('planned_hours'):
1028                 # 'Computes duration' in days
1029                 plan = 0.0
1030                 if val.get('date') and  val.get('date_deadline'):
1031                     start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
1032                     end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
1033                     diff = end - start
1034                     plan = (diff.seconds/float(86400) + diff.days) * obj_tm.factor
1035                 val['planned_hours'] = plan
1036             else:
1037                 # Converts timedelta into hours
1038                 hours = (val['planned_hours'].seconds / float(3600)) + \
1039                                         (val['planned_hours'].days * 24)
1040                 val['planned_hours'] = hours
1041             exists, r_id = base_calendar.uid2openobjectid(cr, val['id'], self._name, val.get('recurrent_id'))
1042             val.pop('id')
1043             if exists:
1044                 self.write(cr, uid, [exists], val)
1045                 ids.append(exists)
1046             else:
1047                 task_id = self.create(cr, uid, val)
1048                 ids.append(task_id)
1049         return ids
1050         
1051     def export_cal(self, cr, uid, ids, context={}):
1052         task_datas = self.read(cr, uid, ids, [], context ={'read': True})
1053         tasks = []
1054         for task in task_datas:
1055             if task.get('planned_hours', None) and task.get('date_deadline', None):
1056                 task.pop('planned_hours')
1057             tasks.append(task)
1058         todo_obj = self.pool.get('basic.calendar.todo')
1059         ical = todo_obj.export_cal(cr, uid, tasks, context={'model': self._name})
1060         calendar_val = ical.serialize()
1061         calendar_val = calendar_val.replace('"', '').strip()
1062         return calendar_val
1063
1064 calendar_todo()
1065  
1066 class ir_attachment(osv.osv):
1067     _name = 'ir.attachment'
1068     _inherit = 'ir.attachment'
1069
1070     def search_count(self, cr, user, args, context=None):
1071         args1 = []
1072         for arg in args:
1073             args1.append(map(lambda x:str(x).split('-')[0], arg))
1074         return super(ir_attachment, self).search_count(cr, user, args1, context)
1075
1076     def search(self, cr, uid, args, offset=0, limit=None, order=None, 
1077             context=None, count=False):
1078         new_args = args
1079         for i, arg in enumerate(new_args):
1080             if arg[0] == 'res_id':
1081                 new_args[i] = (arg[0], arg[1], caldav_id2real_id(arg[2]))
1082         return super(ir_attachment, self).search(cr, uid, new_args, offset=offset, 
1083                             limit=limit, order=order, 
1084                             context=context, count=False)
1085 ir_attachment()
1086
1087 class ir_values(osv.osv):
1088     _inherit = 'ir.values'
1089
1090     def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1091             isobject=False, meta=False, preserve_user=False, company=False):
1092         new_model = []
1093         for data in models:
1094             if type(data) in (list, tuple):
1095                 new_model.append((data[0], caldav_id2real_id(data[1])))
1096             else:
1097                 new_model.append(data)
1098         return super(ir_values, self).set(cr, uid, key, key2, name, new_model,\
1099                     value, replace, isobject, meta, preserve_user, company)
1100
1101     def get(self, cr, uid, key, key2, models, meta=False, context={}, \
1102              res_id_req=False, without_user=True, key2_req=True):
1103         new_model = []
1104         for data in models:
1105             if type(data) in (list, tuple):
1106                 new_model.append((data[0], caldav_id2real_id(data[1])))
1107             else:
1108                 new_model.append(data)
1109         return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1110                          meta, context, res_id_req, without_user, key2_req)
1111
1112 ir_values()
1113
1114 class ir_model(osv.osv):
1115
1116     _inherit = 'ir.model'
1117
1118     def read(self, cr, uid, ids, fields=None, context={}, 
1119             load='_classic_read'):
1120         data = super(ir_model, self).read(cr, uid, ids, fields=fields, \
1121                         context=context, load=load)
1122         if data:
1123             for val in data:
1124                 val['id'] = caldav_id2real_id(val['id'])
1125         return data
1126
1127 ir_model()
1128
1129 class virtual_report_spool(web_services.report_spool):
1130
1131     def exp_report(self, db, uid, object, ids, datas=None, context=None):
1132         if object == 'printscreen.list':
1133             return super(virtual_report_spool, self).exp_report(db, uid, \
1134                             object, ids, datas, context)
1135         new_ids = []
1136         for id in ids:
1137             new_ids.append(caldav_id2real_id(id))
1138         datas['id'] = caldav_id2real_id(datas['id'])        
1139         return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1140
1141 virtual_report_spool()
1142
1143 class calendar_custom_rrule(osv.osv):
1144     _name = "calendar.custom.rrule"
1145     _description = "Custom Recurrency Rule"
1146
1147     _columns = {
1148         'freq': fields.selection([('None', 'No Repeat'), \
1149                             ('secondly', 'Secondly'), \
1150                             ('minutely', 'Minutely'), \
1151                             ('hourly', 'Hourly'), \
1152                             ('daily', 'Daily'), \
1153                             ('weekly', 'Weekly'), \
1154                             ('monthly', 'Monthly'), \
1155                             ('yearly', 'Yearly')], 'Frequency', required=True), 
1156         'interval': fields.integer('Interval'), 
1157         'count': fields.integer('Count'), 
1158         'mo': fields.boolean('Mon'), 
1159         'tu': fields.boolean('Tue'), 
1160         'we': fields.boolean('Wed'), 
1161         'th': fields.boolean('Thu'), 
1162         'fr': fields.boolean('Fri'), 
1163         'sa': fields.boolean('Sat'), 
1164         'su': fields.boolean('Sun'), 
1165         'select1': fields.selection([('date', 'Date of month'), \
1166                             ('day', 'Day of month')], 'Option'), 
1167         'day': fields.integer('Date of month'), 
1168         'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
1169                                    ('WE', 'Wednesday'), ('TH', 'Thursday'), \
1170                                    ('FR', 'Friday'), ('SA', 'Saturday'), \
1171                                    ('SU', 'Sunday')], 'Weekday'), 
1172         'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
1173                                    ('3', 'Third'), ('4', 'Fourth'), \
1174                                    ('5', 'Fifth'), ('-1', 'Last')], 'By day'), 
1175         'month_list': fields.selection(months.items(), 'Month'), 
1176         'end_date': fields.date('Repeat Until')
1177     }
1178
1179     _defaults = {
1180                  'freq':  lambda *x: 'daily', 
1181                  'select1':  lambda *x: 'date', 
1182                  'interval':  lambda *x: 1, 
1183                  }
1184
1185     def compute_rule_string(self, cr, uid, datas, context=None, *args):
1186         weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
1187         weekstring = ''
1188         monthstring = ''
1189         yearstring = ''
1190
1191 #    logic for computing rrule string
1192
1193         freq = datas.get('freq')
1194         if freq == 'None':
1195             obj.write(cr, uid, [res_obj.id], {'rrule': ''})
1196             return {}
1197
1198         if freq == 'weekly':
1199             byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
1200             if byday:
1201                 weekstring = ';BYDAY=' + ','.join(byday)
1202
1203         elif freq == 'monthly':
1204             if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
1205                 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1206             if datas.get('select1')=='day':
1207                 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1208             elif datas.get('select1')=='date':
1209                 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
1210
1211         elif freq == 'yearly':
1212             if datas.get('select1')=='date'  and (datas.get('day') < 1 or datas.get('day') > 31):
1213                 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
1214             bymonth = ';BYMONTH=' + str(datas.get('month_list'))
1215             if datas.get('select1')=='day':
1216                 bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
1217             elif datas.get('select1')=='date':
1218                 bystring = ';BYMONTHDAY=' + str(datas.get('day'))
1219             yearstring = bymonth + bystring
1220
1221         if datas.get('end_date'):
1222             datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + '235959Z'
1223         enddate = (datas.get('count') and (';COUNT=' +  str(datas.get('count'))) or '') +\
1224                              ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
1225
1226         rrule_string = 'FREQ=' + freq.upper() +  weekstring + ';INTERVAL=' + \
1227                 str(datas.get('interval')) + enddate + monthstring + yearstring
1228
1229 #        End logic
1230         return rrule_string
1231
1232     def do_add(self, cr, uid, ids, context={}):
1233         datas = self.read(cr, uid, ids)[0]
1234         if datas.get('interval') <= 0:
1235             raise osv.except_osv(_('Error!'), ("Please select proper Interval"))
1236
1237
1238         if not context or not context.get('model'):
1239             return {}
1240         else:
1241             model = context.get('model')
1242         obj = self.pool.get(model)
1243         res_obj = obj.browse(cr, uid, context['active_id'])
1244
1245         rrule_string = self.compute_rule_string(cr, uid, datas)
1246         obj.write(cr, uid, [res_obj.id], {'rrule': rrule_string})
1247         return {}
1248
1249 calendar_custom_rrule()
1250
1251 class res_users(osv.osv):
1252     _inherit = 'res.users'
1253
1254     def _get_user_avail(self, cr, uid, ids, context=None):
1255         current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1256         res = {}
1257         attendee_obj = self.pool.get('calendar.attendee')
1258         attendee_ids = attendee_obj.search(cr, uid, [
1259                     ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime), 
1260                     ('state', '=', 'accepted'), ('user_id', 'in', ids)
1261                     ])
1262
1263         result = cr.dictfetchall()
1264         for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1265             user_id = attendee_data['user_id']
1266             status = 'busy'
1267             res.update({user_id:status})
1268
1269         #TOCHECK: Delegrated Event        
1270         for user_id in ids:
1271             if user_id not in res:
1272                 res[user_id] = 'free'
1273
1274         return res
1275
1276     def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1277         return self._get_user_avail(cr, uid, ids, context=context)
1278
1279     _columns = {
1280             'availability': fields.function(_get_user_avail_fun, type='selection', \
1281                     selection=[('free', 'Free'), ('busy', 'Busy')], \
1282                     string='Free/Busy', method=True), 
1283     }
1284 res_users()
1285 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: