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