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