1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
21 from datetime import datetime, timedelta
22 from dateutil import parser
23 from dateutil import rrule
24 from osv import fields, osv
25 from service import web_services
26 from tools.translate import _
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"}
39 def get_recurrent_dates(rrulestring, exdate, startdate=None):
41 val = parser.parse(''.join((re.compile('\d')).findall(date)))
44 startdate = datetime.now()
45 rset1 = rrule.rrulestr(rrulestring, dtstart=startdate, forceset=True)
48 datetime_obj = todate(date)
49 rset1._exdate.append(datetime_obj)
50 re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
53 def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
54 if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
55 res = base_calendar_id.split('-')
59 real_date = time.strftime("%Y-%m-%d %H:%M:%S", \
60 time.strptime(res[1], "%Y%m%d%H%M%S"))
61 start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S")
62 end = start + timedelta(hours=with_date)
63 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
65 return base_calendar_id and int(base_calendar_id) or base_calendar_id
67 def real_id2base_calendar_id(real_id, recurrent_date):
68 if real_id and recurrent_date:
69 recurrent_date = time.strftime("%Y%m%d%H%M%S", \
70 time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
71 return '%d-%s' % (real_id, recurrent_date)
75 def _links_get(self, cr, uid, context={}):
76 obj = self.pool.get('res.request.link')
77 ids = obj.search(cr, uid, [])
78 res = obj.read(cr, uid, ids, ['object', 'name'], context)
79 return [(r['object'], r['name']) for r in res]
84 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
85 <title>%(name)s</title>
88 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
89 style="font-family: Arial, Sans-serif; font-size: 14">
91 <td width="100%%">Hello,</td>
94 <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
97 <td width="100%%">Below are the details of event:</td>
101 <table cellspacing="0" cellpadding="5" border="0" summary=""
102 style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
103 <tr valign="center" align="center">
104 <td bgcolor="DFDFDF">
110 <table cellpadding="8" cellspacing="0" border="0"
111 style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
115 <div><b>Start Date</b></div>
118 <td>%(start_date)s</td>
120 <div><b>End Date</b></div>
123 <td width="25%%">%(end_date)s</td>
126 <td><b>Description</b></td>
128 <td colspan="3">%(description)s</td>
132 <div><b>Location</b></div>
135 <td colspan="3">%(location)s</td>
139 <div><b>Event Attendees</b></div>
144 <div>%(attendees)s</div>
149 <td><b>Are you coming?</b></td>
163 <table border="0" cellspacing="10" cellpadding="0" width="100%%"
164 style="font-family: Arial, Sans-serif; font-size: 14">
166 <td width="100%%"><b>Note:</b> If you are interested please reply this
167 mail and keep only your response from options <i>YES, NO</i>
168 and <i>MAYBE</i>.</td>
171 <td width="100%%">From:</td>
174 <td width="100%%">%(user)s</td>
177 <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
180 <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
187 class invite_attendee_wizard(osv.osv_memory):
188 _name = "base_calendar.invite.attendee"
189 _description = "Invite Attendees"
192 'type': fields.selection([('internal', 'Internal User'), \
193 ('external', 'External Email'), \
194 ('partner', 'Partner Contacts')], 'Type', required=True),
195 'user_ids': fields.many2many('res.users', 'invite_user_rel',
196 'invite_id', 'user_id', 'Users'),
197 'partner_id': fields.many2one('res.partner', 'Partner'),
198 'email': fields.char('Email', size=124),
199 'contact_ids': fields.many2many('res.partner.address', 'invite_contact_rel',
200 'invite_id', 'contact_id', 'Contacts'),
201 'send_mail': fields.boolean('Send mail?', help='Check this if you want\
202 to send an Email to Invited Person')
206 'type': lambda *x: 'internal'
209 def do_invite(self, cr, uid, ids, context={}):
210 datas = self.read(cr, uid, ids)[0]
213 if not context or not context.get('model'):
216 model = context.get('model')
217 model_field = context.get('attendee_field', False)
218 obj = self.pool.get(model)
219 res_obj = obj.browse(cr, uid, context['active_id'])
220 type = datas.get('type')
221 att_obj = self.pool.get('calendar.attendee')
224 if not model == 'calendar.attendee':
225 vals = {'ref': '%s,%s' % (model, base_calendar_id2real_id(context['active_id']))}
227 if type == 'internal':
228 user_obj = self.pool.get('res.users')
229 for user_id in datas.get('user_ids', []):
230 user = user_obj.browse(cr, uid, user_id)
231 vals.update({'user_id': user_id,
232 'email': user.address_id.email})
233 if user.address_id.email:
234 mail_to.append(user.address_id.email)
236 elif type == 'external' and datas.get('email'):
237 vals.update({'email': datas['email']})
238 mail_to.append(datas['email'])
239 elif type == 'partner':
240 add_obj = self.pool.get('res.partner.address')
241 for contact in add_obj.browse(cr, uid, datas['contact_ids']):
243 'partner_address_id': contact.id,
244 'email': contact.email})
246 mail_to.append(contact.email)
248 if model == 'calendar.attendee':
249 att = att_obj.browse(cr, uid, context['active_id'])
251 'parent_ids' : [(4, att.id)],
254 att_id = att_obj.create(cr, uid, vals)
256 obj.write(cr, uid, res_obj.id, {model_field: [(4, att_id)]})
258 if datas.get('send_mail'):
260 name = map(lambda x: x[1], filter(lambda x: type==x[0], \
261 self._columns['type'].selection))
262 raise osv.except_osv(_('Error!'), ("%s must have an email \
263 Address to send mail") % (name[0]))
264 att_obj._send_mail(cr, uid, [att_id], mail_to, \
265 email_from=tools.config.get('email_from', False))
270 def onchange_partner_id(self, cr, uid, ids, partner_id, *args, **argv):
272 return {'value': {'contact_ids': []}}
273 cr.execute('select id from res_partner_address \
274 where partner_id=%s' % (partner_id))
275 contacts = map(lambda x: x[0], cr.fetchall())
276 return {'value': {'contact_ids': contacts}}
278 invite_attendee_wizard()
280 class calendar_attendee(osv.osv):
281 _name = 'calendar.attendee'
282 _description = 'Attendee information'
287 def _get_address(self, name=None, email=None):
290 return (name or '') + (email and ('MAILTO:' + email) or '')
292 def _compute_data(self, cr, uid, ids, name, arg, context):
296 for attdata in self.browse(cr, uid, ids, context=context):
299 if name == 'sent_by':
300 if not attdata.sent_by_uid:
301 result[id][name] = ''
304 result[id][name] = self._get_address(attdata.sent_by_uid.name, \
305 attdata.sent_by_uid.address_id.email)
308 result[id][name] = self._get_address(attdata.user_id.name, attdata.email)
309 elif attdata.partner_address_id:
310 result[id][name] = self._get_address(attdata.partner_id.name, attdata.email)
312 result[id][name] = self._get_address(None, attdata.email)
313 if name == 'delegated_to':
315 for parent in attdata.parent_ids:
316 todata.append('MAILTO:' + parent.email)
317 result[id][name] = ', '.join(todata)
318 if name == 'delegated_from':
320 for child in attdata.child_ids:
321 fromdata.append('MAILTO:' + child.email)
322 result[id][name] = ', '.join(fromdata)
323 if name == 'event_date':
325 model, res_id = tuple(attdata.ref.split(','))
326 model_obj = self.pool.get(model)
327 obj = model_obj.read(cr, uid, res_id, ['date'])[0]
328 result[id][name] = obj.get('date')
330 result[id][name] = False
331 if name == 'event_end_date':
333 model, res_id = tuple(attdata.ref.split(','))
334 model_obj = self.pool.get(model)
335 obj = model_obj.read(cr, uid, res_id, ['date_deadline'])[0]
336 result[id][name] = obj.get('date_deadline')
338 result[id][name] = False
339 if name == 'sent_by_uid':
341 model, res_id = tuple(attdata.ref.split(','))
342 model_obj = self.pool.get(model)
343 obj = model_obj.read(cr, uid, res_id, ['user_id'])[0]
344 result[id][name] = obj.get('user_id')
346 result[id][name] = uid
347 if name == 'language':
348 user_obj = self.pool.get('res.users')
349 lang = user_obj.read(cr, uid, uid, ['context_lang'])['context_lang']
350 result[id][name] = lang.replace('_', '-')
353 def _links_get(self, cr, uid, context={}):
354 obj = self.pool.get('res.request.link')
355 ids = obj.search(cr, uid, [])
356 res = obj.read(cr, uid, ids, ['object', 'name'], context)
357 return [(r['object'], r['name']) for r in res]
359 def _lang_get(self, cr, uid, context={}):
360 obj = self.pool.get('res.lang')
361 ids = obj.search(cr, uid, [])
362 res = obj.read(cr, uid, ids, ['code', 'name'], context)
363 res = [((r['code']).replace('_', '-'), r['name']) for r in res]
367 'cutype': fields.selection([('individual', 'Individual'), \
368 ('group', 'Group'), ('resource', 'Resource'), \
369 ('room', 'Room'), ('unknown', '') ], \
370 'Invite Type', help="Specify the type of Invitation"),
371 'member': fields.char('Member', size=124,
372 help="Indicate the groups that the attendee belongs to"),
373 'role': fields.selection([('req-participant', 'Participation required'), \
374 ('chair', 'Chair Person'), \
375 ('opt-participant', 'Optional Participation'), \
376 ('non-participant', 'For information Purpose')], 'Role', \
377 help='Participation role for the calendar user'),
378 'state': fields.selection([('tentative', 'Tentative'),
379 ('needs-action', 'Needs Action'),
380 ('accepted', 'Accepted'),
381 ('declined', 'Declined'),
382 ('delegated', 'Delegated')], 'State', readonly=True,
383 help="Status of the attendee's participation"),
384 'rsvp': fields.boolean('Required Reply?',
385 help="Indicats whether the favor of a reply is requested"),
386 'delegated_to': fields.function(_compute_data, method=True, \
387 string='Delegated To', type="char", size=124, store=True, \
388 multi='delegated_to', help="The users that the original \
389 request was delegated to"),
390 'delegated_from': fields.function(_compute_data, method=True, string=\
391 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
392 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', 'attendee_id', 'parent_id', 'Delegrated From'),
393 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', 'attendee_id', 'child_id', 'Delegrated To'),
394 '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"),
395 'sent_by_uid': fields.function(_compute_data, method=True, string='Sent By User', type="many2one", relation="res.users", multi='sent_by_uid'),
396 'cn': fields.function(_compute_data, method=True, string='Common name', type="char", size=124, multi='cn', store=True),
397 'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."),
398 'language': fields.function(_compute_data, method=True, string='Language', type="selection", selection=_lang_get, multi='language', store=True, help="To specify the language for text values in a property or property parameter."),
399 'user_id': fields.many2one('res.users', 'User'),
400 'partner_address_id': fields.many2one('res.partner.address', 'Contact'),
401 'partner_id': fields.related('partner_address_id', 'partner_id', type='many2one', relation='res.partner', string='Partner', help="Partner related to contact"),
402 'email': fields.char('Email', size=124, help="Email of Invited Person"),
403 'event_date': fields.function(_compute_data, method=True, string='Event Date', type="datetime", multi='event_date'),
404 'event_end_date': fields.function(_compute_data, method=True, string='Event End Date', type="datetime", multi='event_end_date'),
405 'ref': fields.reference('Event Ref', selection=_links_get, size=128),
406 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
409 'state': lambda *x: 'needs-action',
412 response_re = re.compile("Are you coming\?.*\n*.*(YES|NO|MAYBE).*", re.UNICODE)
414 def msg_new(self, cr, uid, msg):
417 def msg_act_get(self, msg):
418 mailgate_obj = self.pool.get('mail.gateway')
419 body = mailgate_obj.msg_body_get(msg)
421 res = self.response_re.findall(body['body'])
423 actions['state'] = res[0]
426 def msg_update(self, cr, uid, ids, msg, data={}, default_act='None'):
427 msg_actions = self.msg_act_get(msg)
428 if msg_actions.get('state'):
429 if msg_actions['state'] in ['YES', 'NO', 'MAYBE']:
430 mapping = {'YES': 'accepted', 'NO': 'declined', 'MAYBE': 'tentative'}
431 status = mapping[msg_actions['state']]
432 print 'Got response for invitation id: %s as %s' % (ids, status)
433 self.write(cr, uid, ids, {'state': status})
436 def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context={}):
437 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
438 for att in self.browse(cr, uid, ids, context=context):
439 sign = att.sent_by_uid and att.sent_by_uid.signature or ''
440 sign = '<br>'.join(sign and sign.split('\n') or [])
441 model, res_id = tuple(att.ref.split(','))
442 res_obj = self.pool.get(model).browse(cr, uid, res_id)
443 if res_obj and len(res_obj):
445 sub = '[%s Invitation][%d] %s' % (company, att.id, res_obj.name)
447 other_invitaion_ids = self.search(cr, uid, [('ref','=',att.ref)])
448 for att2 in self.browse(cr, uid, other_invitaion_ids):
449 att_infos.append(((att2.user_id and att2.user_id.name) or \
450 (att2.partner_id and att2.partner_id.name) or \
451 att2.email) + ' - Status: ' + att2.state.title())
452 body_vals = {'name': res_obj.name,
453 'start_date': res_obj.date,
454 'end_date': res_obj.date_deadline or False,
455 'description': res_obj.description or '-',
456 'location': res_obj.location or '-',
457 'attendees': '<br>'.join(att_infos),
458 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
462 body = html_invitation % body_vals
463 if mail_to and email_from:
473 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
475 return {'value': {'email': ''}}
476 usr_obj = self.pool.get('res.users')
477 user = usr_obj.browse(cr, uid, user_id, *args)
478 return {'value': {'email': user.address_id.email, 'availability':user.availability}}
480 def do_tentative(self, cr, uid, ids, context=None, *args):
481 self.write(cr, uid, ids, {'state': 'tentative'}, context)
483 def do_accept(self, cr, uid, ids, context=None, *args):
484 vals = self.read(cr, uid, ids, context=context)[0]
485 user = vals.get('user_id')
487 ref = vals.get('ref', None)
489 model, event = ref.split(',')
490 model_obj = self.pool.get(model)
491 event_ref = model_obj.browse(cr, uid, event, context=context)[0]
492 if event_ref.user_id.id != user[0]:
493 defaults = {'user_id': user[0]}
494 new_event = model_obj.copy(cr, uid, event, default=defaults, context=context)
495 self.write(cr, uid, ids, {'state': 'accepted'}, context)
497 def do_decline(self, cr, uid, ids, context=None, *args):
498 self.write(cr, uid, ids, {'state': 'declined'}, context)
500 def create(self, cr, uid, vals, context={}):
501 if not vals.get("email") and vals.get("cn"):
502 cnval = vals.get("cn").split(':')
503 email = filter(lambda x:x.__contains__('@'), cnval)
504 vals['email'] = email[0]
505 vals['cn'] = vals.get("cn")
506 res = super(calendar_attendee, self).create(cr, uid, vals, context)
511 class res_alarm(osv.osv):
513 _description = 'Basic Alarm Information'
515 'name':fields.char('Name', size=256, required=True),
516 'trigger_occurs': fields.selection([('before', 'Before'), ('after', 'After')], \
517 'Triggers', required=True),
518 'trigger_interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), \
519 ('days', 'Days')], 'Interval', required=True),
520 'trigger_duration': fields.integer('Duration', required=True),
521 'trigger_related': fields.selection([('start', 'The event starts'), ('end', \
522 'The event ends')], 'Related to', required=True),
523 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
524 are both optional, but if one occurs, so MUST the other"""),
525 'repeat': fields.integer('Repeat'),
526 '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."),
531 'trigger_interval': lambda *x: 'minutes',
532 'trigger_duration': lambda *x: 5,
533 'trigger_occurs': lambda *x: 'before',
534 'trigger_related': lambda *x: 'start',
535 'active': lambda *x: 1,
538 def do_alarm_create(self, cr, uid, ids, model, date, context={}):
539 alarm_obj = self.pool.get('calendar.alarm')
540 ir_obj = self.pool.get('ir.model')
541 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
543 model_obj = self.pool.get(model)
544 for data in model_obj.browse(cr, uid, ids):
545 basic_alarm = data.alarm_id
546 if not context.get('alarm_id'):
547 self.do_alarm_unlink(cr, uid, [data.id], model)
549 self.do_alarm_unlink(cr, uid, [data.id], model)
553 'description': data.description,
555 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
556 'trigger_related': basic_alarm.trigger_related,
557 'trigger_duration': basic_alarm.trigger_duration,
558 'trigger_occurs': basic_alarm.trigger_occurs,
559 'trigger_interval': basic_alarm.trigger_interval,
560 'duration': basic_alarm.duration,
561 'repeat': basic_alarm.repeat,
563 'event_date': data[date],
565 'model_id': model_id,
568 alarm_id = alarm_obj.create(cr, uid, vals)
569 cr.execute('Update %s set base_calendar_alarm_id=%s, alarm_id=%s \
570 where id=%s' % (model_obj._table, \
571 alarm_id, basic_alarm.id, data.id))
575 def do_alarm_unlink(self, cr, uid, ids, model, context={}):
576 alarm_obj = self.pool.get('calendar.alarm')
577 ir_obj = self.pool.get('ir.model')
578 model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
579 model_obj = self.pool.get(model)
580 for datas in model_obj.browse(cr, uid, ids):
581 alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
583 alarm_obj.unlink(cr, uid, alarm_ids)
584 cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
585 where id=%s' % (model_obj._table, datas.id))
591 class calendar_alarm(osv.osv):
592 _name = 'calendar.alarm'
593 _description = 'Event alarm information'
594 _inherit = 'res.alarm'
598 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
599 'name': fields.char('Summary', size=124, help="""Contains the text to be used as the message subject for email
600 or contains the text to be used for display"""),
601 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
602 ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
603 required=True, help="Defines the action to be invoked when an alarm is triggered"),
604 'description': fields.text('Description', help='Provides a more complete description of the calendar component, than that provided by the "SUMMARY" property'),
605 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
606 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
607 'attach': fields.binary('Attachment', help="""* Points to a sound resource, which is rendered when the alarm is triggered for audio,
608 * File which is intended to be sent as message attachments for email,
609 * Points to a procedure resource, which is invoked when the alarm is triggered for procedure."""),
610 'res_id': fields.integer('Resource ID'),
611 'model_id': fields.many2one('ir.model', 'Model'),
612 'user_id': fields.many2one('res.users', 'Owner'),
613 'event_date': fields.datetime('Event Date'),
614 'event_end_date': fields.datetime('Event End Date'),
615 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
616 'state':fields.selection([
621 ], 'State', select=True, readonly=True),
625 'action': lambda *x: 'email',
626 'state': lambda *x: 'run',
629 def create(self, cr, uid, vals, context={}):
630 event_date = vals.get('event_date', False)
632 dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
633 if vals['trigger_interval'] == 'days':
634 delta = timedelta(days=vals['trigger_duration'])
635 if vals['trigger_interval'] == 'hours':
636 delta = timedelta(hours=vals['trigger_duration'])
637 if vals['trigger_interval'] == 'minutes':
638 delta = timedelta(minutes=vals['trigger_duration'])
639 trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
640 vals['trigger_date'] = trigger_date
641 res = super(calendar_alarm, self).create(cr, uid, vals, context)
644 def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False,\
648 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
649 cr.execute("select alarm.id as id \
650 from calendar_alarm alarm \
651 where alarm.state = %s and alarm.trigger_date <= %s", ('run', current_datetime))
652 res = cr.dictfetchall()
653 alarm_ids = map(lambda x: x['id'], res)
654 attendee_obj = self.pool.get('calendar.attendee')
655 request_obj = self.pool.get('res.request')
657 for alarm in self.browse(cr, uid, alarm_ids):
658 if alarm.action == 'display':
661 'act_from': alarm.user_id.id,
662 'act_to': alarm.user_id.id,
663 'body': alarm.description,
664 'trigger_date': alarm.trigger_date,
665 'ref_doc1': '%s,%s' % (alarm.model_id.model, alarm.res_id)
667 request_id = request_obj.create(cr, uid, value)
668 request_ids = [request_id]
669 for attendee in alarm.attendee_ids:
671 value['act_to'] = attendee.user_id.id
672 request_id = request_obj.create(cr, uid, value)
673 request_ids.append(request_id)
674 request_obj.request_send(cr, uid, request_ids)
676 if alarm.action == 'email':
677 sub = '[Openobject Remainder] %s' % (alarm.name)
687 """ % (alarm.name, alarm.trigger_date, alarm.description, \
688 alarm.user_id.name, alarm.user_id.signature)
689 mail_to = [alarm.user_id.address_id.email]
690 for att in alarm.attendee_ids:
691 mail_to.append(att.user_id.address_id.email)
694 tools.config.get('email_from', False),
699 self.write(cr, uid, [alarm.id], {'state':'done'})
705 class calendar_event(osv.osv):
706 _name = "calendar.event"
707 _description = "Calendar Event"
710 def _tz_get(self, cr, uid, context={}):
711 return [(x.lower(), x) for x in pytz.all_timezones]
714 def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, context={}):
717 start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
719 if end_date and not duration:
720 end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
722 duration = float(diff.days)* 24 + (float(diff.seconds) / 3600)
723 value['duration'] = round(duration, 2)
725 end = start + timedelta(hours=duration)
726 value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
727 return {'value': value}
729 def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
732 datas = self.read(cr, uid, ids)[0]
733 if datas.get('rrule_type'):
734 if datas.get('rrule_type') == 'none':
735 result[event] = False
736 elif datas.get('rrule_type') == 'custom':
737 rrule_custom = self.compute_rule_string(cr, uid, datas)
738 result[event] = rrule_custom
740 result[event] = self.compute_rule_string(cr, uid, {'freq':\
741 datas.get('rrule_type').upper(), \
742 'interval': 1}, context=context)
746 'id': fields.integer('ID'),
747 'sequence': fields.integer('Sequence'),
748 'name': fields.char('Description', size=64, required=True),
749 'date': fields.datetime('Date'),
750 'date_deadline': fields.datetime('Deadline'),
751 'create_date': fields.datetime('Created', readonly=True),
752 'duration': fields.float('Duration'),
753 'description': fields.text('Your action'),
754 'class': fields.selection([('public', 'Public'), ('private', 'Private'), \
755 ('confidential', 'Confidential')], 'Mark as'),
756 'location': fields.char('Location', size=264, help="Location of Event"),
757 'show_as': fields.selection([('free', 'Free'), \
760 'base_calendar_url': fields.char('Caldav URL', size=264),
761 'exdate': fields.text('Exception Date/Times', help="This property \
762 defines the list of date/time exceptions for arecurring calendar component."),
763 'exrule': fields.char('Exception Rule', size=352, help="defines a \
764 rule or repeating pattern for anexception to a recurrence set"),
765 'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, string='Recurrent Rule', store=True),
766 'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
767 ('weekly', 'Weekly'), ('monthly', 'Monthly'), \
768 ('yearly', 'Yearly'), ('custom', 'Custom')], 'Recurrency'),
769 'alarm_id': fields.many2one('res.alarm', 'Alarm'),
770 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
771 'recurrent_uid': fields.integer('Recurrent ID'),
772 'recurrent_id': fields.datetime('Recurrent ID date'),
773 'vtimezone': fields.related('user_id', 'context_tz', type='char', size=24, string='Timezone'),
774 'user_id': fields.many2one('res.users', 'Responsible'),
775 'freq': fields.selection([('None', 'No Repeat'), \
776 ('secondly', 'Secondly'), \
777 ('minutely', 'Minutely'), \
778 ('hourly', 'Hourly'), \
779 ('daily', 'Daily'), \
780 ('weekly', 'Weekly'), \
781 ('monthly', 'Monthly'), \
782 ('yearly', 'Yearly')], 'Frequency'),
783 'interval': fields.integer('Interval'),
784 'count': fields.integer('Count'),
785 'mo': fields.boolean('Mon'),
786 'tu': fields.boolean('Tue'),
787 'we': fields.boolean('Wed'),
788 'th': fields.boolean('Thu'),
789 'fr': fields.boolean('Fri'),
790 'sa': fields.boolean('Sat'),
791 'su': fields.boolean('Sun'),
792 'select1': fields.selection([('date', 'Date of month'), \
793 ('day', 'Day of month')], 'Option'),
794 'day': fields.integer('Date of month'),
795 'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
796 ('WE', 'Wednesday'), ('TH', 'Thursday'), \
797 ('FR', 'Friday'), ('SA', 'Saturday'), \
798 ('SU', 'Sunday')], 'Weekday'),
799 'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
800 ('3', 'Third'), ('4', 'Fourth'), \
801 ('5', 'Fifth'), ('-1', 'Last')], 'By day'),
802 'month_list': fields.selection(months.items(), 'Month'),
803 'end_date': fields.date('Repeat Until')
807 'class': lambda *a: 'public',
808 'show_as': lambda *a: 'busy',
809 'freq': lambda *x: 'None',
810 'select1': lambda *x: 'date',
811 'interval': lambda *x: 1,
814 def modify_this(self, cr, uid, ids, defaults, context=None, *args):
815 ids = map(lambda x: base_calendar_id2real_id(x), ids)
816 datas = self.read(cr, uid, ids[0], context=context)
818 'recurrent_uid': base_calendar_id2real_id(datas['id']),
819 'recurrent_id': defaults.get('date'),
820 'rrule_type': 'none',
823 exdate = datas['exdate'] and datas['exdate'].split(',') or []
824 if defaults.get('date'):
825 exdate.append(defaults.get('date'))
826 self.write(cr, uid, ids, {'exdate': ','.join(exdate)}, context=context)
827 new_id = self.copy(cr, uid, ids[0], default=defaults, context=context)
830 def modify_all(self, cr, uid, ids, defaults, context=None, *args):
831 ids = map(lambda x: base_calendar_id2real_id(x), ids)
833 defaults.update({'table': self._table})
835 qry = "UPDATE %(table)s set name='%(name)s', \
836 date='%(date)s', date_deadline='%(date_deadline)s'"
837 if defaults.get('alarm_id'):
838 qry += ", alarm_id=%(alarm_id)s"
839 if defaults.get('location'):
840 qry += ", location='%(location)s'"
841 qry += "WHERE id=%s" % (ids[0])
842 cr.execute(qry % (defaults))
845 def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
848 if isinstance(select, (str, int, long)):
853 if ids and (base_start_date or base_until_date):
854 cr.execute("select m.id, m.rrule, m.date, m.date_deadline, \
855 m.exdate from " + self._table + \
856 " m where m.id in ("\
857 + ','.join(map(lambda x: str(x), ids))+")")
860 for data in cr.dictfetchall():
861 start_date = base_start_date and datetime.strptime(base_start_date, "%Y-%m-%d") or False
862 until_date = base_until_date and datetime.strptime(base_until_date, "%Y-%m-%d") or False
865 event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
866 if start_date and start_date <= event_date:
867 start_date = event_date
868 if not data['rrule']:
869 if start_date and (event_date < start_date):
871 if until_date and (event_date > until_date):
873 idval = real_id2base_calendar_id(data['id'], data['date'])
877 exdate = data['exdate'] and data['exdate'].split(',') or []
878 rrule_str = data['rrule']
880 rrule_until_date = False
882 for rule in rrule_str.split(';'):
883 name, value = rule.split('=')
886 value = parser.parse(value)
887 rrule_until_date = parser.parse(value.strftime("%Y-%m-%d"))
888 if until_date and until_date >= rrule_until_date:
889 until_date = rrule_until_date
891 value = until_date.strftime("%Y%m%d%H%M%S")
892 new_rule = '%s=%s' % (name, value)
893 new_rrule_str.append(new_rule)
894 if not is_until and until_date:
895 value = until_date.strftime("%Y%m%d%H%M%S")
897 new_rule = '%s=%s' % (name, value)
898 new_rrule_str.append(new_rule)
899 new_rrule_str = ';'.join(new_rrule_str)
900 start_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
901 rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date)
903 r_date = datetime.strptime(rdate, "%Y-%m-%d %H:%M:%S")
904 if start_date and r_date < start_date:
906 if until_date and r_date > until_date:
908 idval = real_id2base_calendar_id(data['id'], rdate)
913 if isinstance(select, (str, int, long)):
914 return ids and ids[0] or False
917 def compute_rule_string(self, cr, uid, datas, context=None, *args):
918 weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
922 # logic for computing rrule string
923 freq = datas.get('freq')
928 byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
930 weekstring = ';BYDAY=' + ','.join(byday)
932 elif freq == 'monthly':
933 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
934 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
935 if datas.get('select1')=='day':
936 monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
937 elif datas.get('select1')=='date':
938 monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
940 elif freq == 'yearly':
941 if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
942 raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
943 bymonth = ';BYMONTH=' + str(datas.get('month_list'))
944 if datas.get('select1')=='day':
945 bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
946 elif datas.get('select1')=='date':
947 bystring = ';BYMONTHDAY=' + str(datas.get('day'))
948 yearstring = bymonth + bystring
950 if datas.get('end_date'):
951 datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + '235959Z'
952 enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
953 ((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
955 rrule_string = 'FREQ=' + freq.upper() + weekstring + ';INTERVAL=' + \
956 str(datas.get('interval')) + enddate + monthstring + yearstring
961 def search(self, cr, uid, args, offset=0, limit=100, order=None,
962 context=None, count=False):
963 args_without_date = []
967 if arg[0] not in ('date', unicode('date')):
968 args_without_date.append(arg)
970 if arg[1] in ('>', '>='):
972 elif arg[1] in ('<', '<='):
974 res = super(calendar_event, self).search(cr, uid, args_without_date, \
975 offset, limit, order, context, count)
976 return self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
979 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
982 if isinstance(ids, (str, int, long)):
988 if len(str(id).split('-')) > 1:
989 data = self.read(cr, uid, id, ['date', 'date_deadline'])
990 self.modify_this(cr, uid, [id], vals, context)
992 id = base_calendar_id2real_id(id)
993 if not id in new_ids:
995 res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
996 if vals.has_key('alarm_id') or vals.has_key('base_calendar_alarm_id'):
997 alarm_obj = self.pool.get('res.alarm')
998 context.update({'alarm_id': vals.get('alarm_id')})
999 alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
1002 def browse(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
1003 if isinstance(ids, (str, int, long)):
1007 select = map(lambda x: base_calendar_id2real_id(x), select)
1008 res = super(calendar_event, self).browse(cr, uid, select, context, list_class, fields_process)
1009 if isinstance(ids, (str, int, long)):
1010 return res and res[0] or False
1013 def read(self, cr, uid, ids, fields=None, context={}, load='_classic_read'):
1014 if isinstance(ids, (str, int, long)):
1018 select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
1020 if fields and 'date' not in fields:
1021 fields.append('date')
1022 for base_calendar_id, real_id in select:
1023 res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, \
1025 ls = base_calendar_id2real_id(base_calendar_id, with_date=res.get('duration', 0))
1026 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1028 res['date_deadline'] = ls[2]
1029 res['id'] = base_calendar_id
1032 if isinstance(ids, (str, int, long)):
1033 return result and result[0] or False
1036 def copy(self, cr, uid, id, default=None, context={}):
1037 res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
1038 alarm_obj = self.pool.get('res.alarm')
1039 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
1042 def unlink(self, cr, uid, ids, context=None):
1045 ls = base_calendar_id2real_id(id)
1046 if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
1048 for record in self.read(cr, uid, [base_calendar_id2real_id(id)], \
1049 ['date', 'rrule', 'exdate']):
1051 exdate = (record['exdate'] and (record['exdate'] + ',') or '') + ''.join((re.compile('\d')).findall(date_new)) + 'Z'
1052 if record['date'] == date_new:
1053 res = self.write(cr, uid, [base_calendar_id2real_id(id)], {'exdate': exdate})
1055 ids = map(lambda x: base_calendar_id2real_id(x), ids)
1056 res = super(calendar_event, self).unlink(cr, uid, base_calendar_id2real_id(ids))
1057 alarm_obj = self.pool.get('res.alarm')
1058 alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
1060 ids = map(lambda x: base_calendar_id2real_id(x), ids)
1061 res = super(calendar_event, self).unlink(cr, uid, ids)
1062 alarm_obj = self.pool.get('res.alarm')
1063 alarm_obj.do_alarm_unlink(cr, uid, ids, self._name)
1066 def create(self, cr, uid, vals, context={}):
1067 res = super(calendar_event, self).create(cr, uid, vals, context)
1068 alarm_obj = self.pool.get('res.alarm')
1069 alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date')
1074 class calendar_todo(osv.osv):
1075 _name = "calendar.todo"
1076 _inherit = "calendar.event"
1077 _description = "Calendar Task"
1079 def _get_date(self, cr, uid, ids, name, arg, context):
1081 for event in self.browse(cr, uid, ids, context=context):
1082 res[event.id] = event.date_start
1085 def _set_date(self, cr, uid, id, name, value, arg, context):
1086 event = self.browse(cr, uid, id, context=context)
1087 cr.execute("UPDATE %s set date_start='%s' where id=%s" \
1088 % (self._table, value, id))
1092 'date': fields.function(_get_date, method=True, fnct_inv=_set_date, \
1093 string='Duration', store=True, type='datetime'),
1094 'duration': fields.integer('Duration'),
1102 class ir_attachment(osv.osv):
1103 _name = 'ir.attachment'
1104 _inherit = 'ir.attachment'
1106 def search_count(self, cr, user, args, context=None):
1109 args1.append(map(lambda x:str(x).split('-')[0], arg))
1110 return super(ir_attachment, self).search_count(cr, user, args1, context)
1112 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1113 context=None, count=False):
1115 for i, arg in enumerate(new_args):
1116 if arg[0] == 'res_id':
1117 new_args[i] = (arg[0], arg[1], base_calendar_id2real_id(arg[2]))
1118 return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
1119 limit=limit, order=order,
1120 context=context, count=False)
1123 class ir_values(osv.osv):
1124 _inherit = 'ir.values'
1126 def set(self, cr, uid, key, key2, name, models, value, replace=True, \
1127 isobject=False, meta=False, preserve_user=False, company=False):
1130 if type(data) in (list, tuple):
1131 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1133 new_model.append(data)
1134 return super(ir_values, self).set(cr, uid, key, key2, name, new_model,\
1135 value, replace, isobject, meta, preserve_user, company)
1137 def get(self, cr, uid, key, key2, models, meta=False, context={}, \
1138 res_id_req=False, without_user=True, key2_req=True):
1141 if type(data) in (list, tuple):
1142 new_model.append((data[0], base_calendar_id2real_id(data[1])))
1144 new_model.append(data)
1145 return super(ir_values, self).get(cr, uid, key, key2, new_model, \
1146 meta, context, res_id_req, without_user, key2_req)
1150 class ir_model(osv.osv):
1152 _inherit = 'ir.model'
1154 def read(self, cr, uid, ids, fields=None, context={},
1155 load='_classic_read'):
1156 data = super(ir_model, self).read(cr, uid, ids, fields=fields, \
1157 context=context, load=load)
1160 val['id'] = base_calendar_id2real_id(val['id'])
1165 class virtual_report_spool(web_services.report_spool):
1167 def exp_report(self, db, uid, object, ids, datas=None, context=None):
1168 if object == 'printscreen.list':
1169 return super(virtual_report_spool, self).exp_report(db, uid, \
1170 object, ids, datas, context)
1173 new_ids.append(base_calendar_id2real_id(id))
1174 datas['id'] = base_calendar_id2real_id(datas['id'])
1175 return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
1177 virtual_report_spool()
1179 class res_users(osv.osv):
1180 _inherit = 'res.users'
1182 def _get_user_avail(self, cr, uid, ids, context=None):
1183 current_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1185 attendee_obj = self.pool.get('calendar.attendee')
1186 attendee_ids = attendee_obj.search(cr, uid, [
1187 ('event_date', '<=', current_datetime), ('event_end_date', '<=', current_datetime),
1188 ('state', '=', 'accepted'), ('user_id', 'in', ids)
1191 result = cr.dictfetchall()
1192 for attendee_data in attendee_obj.read(cr, uid, attendee_ids, ['user_id']):
1193 user_id = attendee_data['user_id']
1195 res.update({user_id:status})
1197 #TOCHECK: Delegated Event
1199 if user_id not in res:
1200 res[user_id] = 'free'
1204 def _get_user_avail_fun(self, cr, uid, ids, name, args, context=None):
1205 return self._get_user_avail(cr, uid, ids, context=context)
1208 'availability': fields.function(_get_user_avail_fun, type='selection', \
1209 selection=[('free', 'Free'), ('busy', 'Busy')], \
1210 string='Free/Busy', method=True),
1214 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: