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