[ADD]: Added new module project_calendar that is used to import/export ical data of project tasks

bzr revid: rpa@tinyerp.com-20091218122420-817voetdl2q61n5r
This commit is contained in:
rpa (Open ERP) 2009-12-18 17:54:20 +05:30
parent 25d298f4b8
commit 0f148f391c
8 changed files with 623 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import project_calendar
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name" : "Caldav task management",
"version" : "1.0",
"author" : "Tiny",
"category" : "Generic Modules/Others",
"description": """ Synchronize between Project task and Caldav Vtodo.""",
"depends" : ["project", "caldav"],
"init_xml" : [],
"demo_xml" : [],
"update_xml" : ["project_calendar_view.xml",
"project_calendar_wizard.xml",
"security/ir.model.access.csv"],
"active": False,
"website": "http://www.openerp.com",
"installable": True,
}

View File

@ -0,0 +1,333 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from osv import fields, osv
from service import web_services
import base64
def caldevIDs2readIDs(caldev_ID = None):
if caldev_ID:
if isinstance(caldev_ID, str):
return int(caldev_ID.split('-')[0])
return caldev_ID
class crm_caldav_attendee(osv.osv):
_name = 'crm.caldav.attendee'
_description = 'Attendee information'
_rec_name = 'cutype'
__attribute__ = {
'cutype': {'field':'cutype', 'type':'text'},
'member': {'field':'member', 'type':'text'},
'role': {'field':'role', 'type':'selection'},
'partstat': {'field':'partstat', 'type':'text'},
'rsvp': {'field':'rsvp', 'type':'boolean'},
'delegated-to': {'field':'delegated_to', 'type':'char'},
'delegated-from': {'field':'delegated_from', 'type':'char'},
'sent-by': {'field':'sent_by', 'type':'text'},
'cn': {'field':'cn', 'type':'text'},
'dir': {'field':'dir', 'type':'text'},
'language': {'field':'language', 'type':'text'},
}
_columns = {
'cutype': fields.selection([('INDIVIDUAL', 'INDIVIDUAL'), ('GROUP', 'GROUP'), \
('RESOURCE', 'RESOURCE'), ('ROOM', 'ROOM'), \
('UNKNOWN', 'UNKNOWN') ], 'CUTYPE'),
'member': fields.char('Member', size=124),
'role': fields.selection([ ('REQ-PARTICIPANT', 'REQ-PARTICIPANT'), \
('CHAIR', 'CHAIR'), ('OPT-PARTICIPANT', 'OPT-PARTICIPANT'), \
('NON-PARTICIPANT', 'NON-PARTICIPANT')], 'ROLE'),
'partstat': fields.selection([('NEEDS-ACTION', 'NEEDS-ACTION'), \
('ACCEPTED', 'ACCEPTED'), ('DECLINED', 'DECLINED'), \
('TENTATIVE', 'TENTATIVE'), \
('DELEGATED', 'DELEGATED')], 'PARTSTAT'),
'rsvp': fields.boolean('RSVP'),
'delegated_to': fields.char('DELEGATED-TO', size=124),
'delegated_from': fields.char('DELEGATED-FROM', size=124),
'sent_by': fields.char('SENT-BY', size=124),
'cn': fields.char('CN', size=124),
'dir': fields.char('DIR', size=124),
'language': fields.char('LANGUAGE', size=124),
}
_defaults = {
'cn': lambda *x: 'MAILTO:',
}
crm_caldav_attendee()
class crm_caldav_alarm(osv.osv):
_name = 'crm.caldav.alarm'
_description = 'Event alarm information'
__attribute__ = {
'action': {'field': 'action', 'type': 'text'},
'description': {'field': 'name', 'type': 'text'},
'summary': {'field': 'description', 'type': 'text'},
'attendee': {'field': 'attendee_ids', 'type': 'text'},
'trigger_related': {'field': 'trigger_related', 'type': 'text'},
'trigger_duration': {'field': 'trigger_duration', 'type': 'text'},
'trigger_occurs': {'field': 'trigger_occurs', 'type': 'text'},
'trigger_interval': {'field': 'trigger_interval', 'type': 'text'},
'duration': {'field': 'duration', 'type': 'text'},
'repeat': {'field': 'repeat', 'type': 'text'},
'attach': {'field': 'attach', 'type': 'text'},
}
_columns = {
'name': fields.char('Summary', size=124),
'action': fields.selection([('AUDIO', 'AUDIO'), ('DISPLAY', 'DISPLAY'), \
('PROCEDURE', 'PROCEDURE'), ('EMAIL', 'EMAIL') ], 'Action', required=True),
'description': fields.text('Description'),
'attendee_ids': fields.many2many('crm.caldav.attendee', 'alarm_attendee_rel', \
'alarm_id', 'attendee_id', 'Attendees'),
'trigger_occurs': fields.selection([('BEFORE', 'BEFORE'), ('AFTER', 'AFTER')], \
'Trigger time', required=True),
'trigger_interval': fields.selection([('MINUTES', 'MINUTES'), ('HOURS', 'HOURS'), \
('DAYS', 'DAYS')], 'Trugger duration', required=True),
'trigger_duration': fields.integer('TIme', required=True),
'trigger_related': fields.selection([('start', 'The event starts'), ('end', \
'The event ends')], 'Trigger Occures at', required=True),
'duration': fields.integer('Duration'),
'repeat': fields.integer('Repeat'), # TODO
'attach': fields.binary('Attachment'),
'active': fields.boolean('Active'),
}
_defaults = {
'action': lambda *x: 'EMAIL',
'trigger_interval': lambda *x: 'MINUTES',
'trigger_duration': lambda *x: 5,
'trigger_occurs': lambda *x: 'BEFORE',
'trigger_related': lambda *x: 'start',
}
crm_caldav_alarm()
class project_task(osv.osv):
_inherit = "project.task"
def _get_rdates(self, cr, uid, ids, name, arg, context=None):
res = {}
context.update({'read':True})
for task in self.read(cr, uid, ids, ['date_start', 'rrule'], context=context):
if task['rrule']:
rule = task['rrule'].split('\n')[0]
if rule.upper().find('COUNT') < 0: # Temp fix, needs review
rule += ';COUNT=5'
exdate = task['rrule'].split('\n')[1:]
todo_obj = self.pool.get('caldav.todo')
res[task['id']] = str(todo_obj.get_recurrent_dates(str(rule), exdate, task['date_start']))
return res
_columns = {
'class': fields.selection([('PUBLIC', 'PUBLIC'), ('PRIVATE', 'PRIVATE'), \
('CONFIDENTIAL', 'CONFIDENTIAL')], 'Class'),
'location': fields.text('Location'),
'rrule': fields.text('Recurrent Rule'),
'rdates': fields.function(_get_rdates, method=True, string='Recurrent Dates', \
store=True, type='text'),
'attendee_ids': fields.many2many('crm.caldav.attendee', 'crm_attendee_rel', 'case_id', \
'attendee_id', 'Attendees'),
'alarm_id': fields.many2one('crm.caldav.alarm', 'Alarm'),
'caldav_url': fields.char('Caldav URL', size=34),
}
__attribute__ = {
'class': {'field': 'class', 'type': 'text'},
'completed': {'field': 'date_close', 'type': 'datetime'},
'description': {'field': 'description', 'type': 'text'},
'dtstart': {'field': 'date_start', 'type': 'datetime'},
'duration': {'field': 'planned_hours', 'type': 'int'},
'due': {'field': 'date_deadline', 'type': 'datetime'},
'location': {'field': 'location', 'type': 'text'}, # To add
'organizer': {'field': 'partner_id', 'type': 'many2one', 'object': 'res.partner'},
'percent': {'field': 'progress_rate', 'type': 'int'},
'priority': {'field': 'priority', 'type': 'text'},
'seq': {'field': 'sequence', 'type': 'text'},
'status': {'field': 'state', 'type': 'selection', 'mapping': {'NEEDS-ACTION': 'draft', \
'COMPLETED': 'done', 'IN-PROCESS': 'open', \
'CANCELLED': 'cancelled'}},
'summary': {'field': 'name', 'type': 'text'},
'url': {'field': 'caldav_url', 'type': 'text'}, # To add
'attendee': {'field': 'attendee_ids', 'type': 'many2many', 'object': 'crm.caldav.attendee'},
# 'categories': {'field': 'type', 'type': 'text'}, # Needs review
'comment': {'field': 'notes', 'type': 'text'},
'rrule': {'field': 'rrule', 'type': 'text'},
}
def import_cal(self, cr, uid, ids, data, context={}):
file_content = base64.decodestring(data['form']['file_path'])
todo_obj = self.pool.get('caldav.todo')
todo_obj.__attribute__.update(self.__attribute__)
attendee_obj = self.pool.get('caldav.attendee')
crm_attendee = self.pool.get('crm.caldav.attendee')
attendee_obj.__attribute__.update(crm_attendee.__attribute__)
alarm_obj = self.pool.get('caldav.alarm')
crm_alarm = self.pool.get('crm.caldav.alarm')
alarm_obj.__attribute__.update(crm_alarm.__attribute__)
vals = todo_obj.import_ical(cr, uid, file_content)
if not vals.has_key('duration'):
# 'Compute duration'
vals['planned_hours'] = 16
task_id = self.create(cr, uid, vals)
return
def read(self, cr, uid, ids, fields=None, context={}, load='_classic_read'):
""" logic for recurrent task
example : 123-20091111170822""", ids, fields, context
if context and context.has_key('read'):
return super(project_task, self).read(cr, uid, ids, fields, context, load)
if not type(ids) == list :
return super(project_task, self).read(cr, uid, caldevIDs2readIDs(ids),\
fields=fields, context=context, load=load)
else:
ids = map(lambda x:caldevIDs2readIDs(x), ids)
res = super(project_task, self).read(cr, uid, ids, fields=fields, context=context, load=load)
read_ids = ",".join([str(x) for x in ids])
if not read_ids:
return []
cr.execute('select id,rrule,rdates from project_task where id in (%s)' % read_ids)
rrules = filter(lambda x: not x['rrule']==None, cr.dictfetchall())
rdates = []
if not rrules:
return res
result = res + []
for data in rrules:
if data['rrule'] and data['rdates']:
rdates = eval(data['rdates'])
for res_temp in res:
if res_temp['id'] == data['id']:
val = res_temp
if rdates:
result.remove(val)
for rdate in rdates:
import re
idval = (re.compile('\d')).findall(rdate)
val['date'] = rdate
id = str(val['id']).split('-')[0]
val['id'] = id + '-' + ''.join(idval)
val1 = val.copy()
result += [val1]
return result
project_task()
class ir_attachment(osv.osv):
_name = 'ir.attachment'
_inherit = 'ir.attachment'
def search_count(self, cr, user, args, context=None):
args1 = []
for arg in args:
args1.append(map(lambda x:str(x).split('-')[0], arg))
return super(ir_attachment, self).search_count(cr, user, args1, context)
def search(self, cr, uid, args, offset=0, limit=None, order=None,
context=None, count=False):
new_args = []
if len(args) > 1:
new_args = [args[0]]
if args[1][0] == 'res_id':
new_args.append((args[1][0], args[1][1], caldevIDs2readIDs(args[1][2])))
if new_args:
args = new_args
return super(ir_attachment, self).search(cr, uid, args, offset=offset,
limit=limit, order=order,
context=context, count=False)
ir_attachment()
class ir_values(osv.osv):
_inherit = 'ir.values'
def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, \
meta=False, preserve_user=False, company=False):
new_model = []
for data in models:
if type(data) in (list, tuple):
new_model.append((data[0], caldevIDs2readIDs(data[1])))
else:
new_model.append(data)
return super(ir_values, self).set(cr, uid, key, key2, name, new_model, value, \
replace, isobject, meta, preserve_user, company)
def get(self, cr, uid, key, key2, models, meta=False, context={}, res_id_req=False, \
without_user=True, key2_req=True):
new_model = []
for data in models:
if type(data) in (list, tuple):
new_model.append((data[0], caldevIDs2readIDs(data[1])))
else:
new_model.append(data)
return super(ir_values, self).get(cr, uid, key, key2, new_model, meta, context, \
res_id_req, without_user, key2_req)
ir_values()
class ir_model(osv.osv):
_inherit = 'ir.model'
def read(self, cr, uid, ids, fields=None, context={},
load='_classic_read'):
data = super(ir_model, self).read(cr, uid, ids, fields=fields, context=context, load=load)
if data:
for val in data:
val['id'] = caldevIDs2readIDs(val['id'])
return data
ir_model()
class virtual_report_spool(web_services.report_spool):
def exp_report(self, db, uid, object, ids, datas=None, context=None):
if object == 'printscreen.list':
return super(virtual_report_spool, self).exp_report(db, uid, object, ids, datas, context)
new_ids = []
for id in ids:
new_ids.append(caldevIDs2readIDs(id))
datas['id'] = caldevIDs2readIDs(datas['id'])
super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
virtual_report_spool()
class virtual_wizard(web_services.wizard):
def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
if wiz_id not in self.wiz_uid:
super(virtual_wizard,self).exp_create(db, uid, 'module.upgrade', datas)
new_ids = []
if 'id' in datas:
datas['id'] = caldevIDs2readIDs(datas['id'])
for id in datas['ids']:
new_ids.append(caldevIDs2readIDs(id))
datas['ids'] = new_ids
res=super(virtual_wizard, self).exp_execute(db, uid, wiz_id, datas, action, context)
return res
virtual_wizard()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="caldav_attendee_form_view" model="ir.ui.view">
<field name="name">crm.case.caldav.attendee</field>
<field name="model">crm.caldav.attendee</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Atendee details">
<field name="cutype" select="1"/>
<field name="member"/>
<field name="role" select="1"/>
<field name="partstat"/>
<field name="rsvp"/>
<field name="delegated_to" select="1"/>
<field name="delegated_from" select="1"/>
<field name="sent_by"/>
<field name="cn" select="1"/>
<field name="dir"/>
<field name="language"/>
</form>
</field>
</record>
<record id="caldav_attendee_tree_view" model="ir.ui.view">
<field name="name">crm.case.caldav.attendee</field>
<field name="model">crm.caldav.attendee</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Atendee details">
<field name="cutype" select="1"/>
<field name="member"/>
<field name="role" select="1"/>
<field name="partstat"/>
<field name="rsvp"/>
<field name="delegated_to" select="1"/>
<field name="delegated_from" select="1"/>
<field name="sent_by"/>
<field name="cn" select="1"/>
<field name="dir"/>
<field name="language"/>
</tree>
</field>
</record>
<record id="caldav_alarm_form_view" model="ir.ui.view">
<field name="name">crm.case.caldav.alarm.form</field>
<field name="model">crm.caldav.alarm</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Alarm details">
<field name="action"/>
<field name="name" select="1" attrs="{'required' : [('action','in',('EMAIL', 'DISPLAY'))]}"/>
<separator string="Description" colspan="4"/>
<field name="description" colspan="4" nolabel="1" attrs="{'required' : [('action','in',('EMAIL'))]}"/>
<group string="Reminder details" col="4" colspan="4">
<field name="trigger_duration" nolabel="1" select="1"/>
<field name="trigger_interval" nolabel="1" select="1"/>
<field name="trigger_occurs" nolabel="1" select="1"/>
<field name="trigger_related" nolabel="1" select="1"/>
</group>
<field name="duration"/>
<field name="repeat"/>
<field name="attach" colspan="4" attrs="{'required' : [('action','in',('AUDIO', 'PROCEDURE'))]}"/>
</form>
</field>
</record>
<record id="caldav_alarm_tree_view" model="ir.ui.view">
<field name="name">crm.case.caldav.alarm.tree</field>
<field name="model">crm.caldav.alarm</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Alarm details">
<field name="name" select="1"/>
<field name="action"/>
<field name="trigger_interval" select="1"/>
<field name="trigger_duration" select="1"/>
<field name="trigger_related" select="1"/>
<field name="trigger_occurs" select="1"/>
<field name="active"/>
</tree>
</field>
</record>
<record id="caldav_project_task_view" model="ir.ui.view">
<field name="name">project.task.caldav.tree</field>
<field name="model">project.task</field>
<field name="type">tree</field>
<field name="inherit_id" ref="project.view_task_tree2"/>
<field name="arch" type="xml">
<field name="sequence" position="before">
<field name="id" widget="char"/>
</field>
</field>
</record>
<record id="view_project_caldav_task_form" model="ir.ui.view">
<field name="name">project.task.caldav.form</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="type">form</field>
<field name="arch" type="xml">
<notebook position="inside">
<page string="CalDav Properties">
<field name="class"/>
<field name="caldav_url" widget="url"/>
<field name="alarm_id"/>
<newline/>
<separator string="Location" colspan="4"/>
<field name="location" colspan="4" nolabel="1"/>
<separator string="RRule Details" colspan="4"/>
<field name="rrule" colspan="4" nolabel="1"/>
<separator string="Recurrenet Dates" colspan="4"/>
<field name="rdates" colspan="4" nolabel="1"/>
</page>
<page string="Attendee">
<separator string="Attendee" colspan="4"/>
<field name="attendee_ids" colspan="4" nolabel="1"/>
</page>
</notebook>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,8 @@
<openerp>
<data>
<wizard string="Import ICS File" model="project.task"
name="caldav.project.import" id="wizard_project_cal_import"
multi="True"
/>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_crm_caldav_attendee","crm.caldav.attendee","model_crm_caldav_attendee",,1,1,1,1
"access_crm_caldav_alarm","crm.caldav.alarm","model_crm_caldav_alarm",,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_crm_caldav_attendee crm.caldav.attendee model_crm_caldav_attendee 1 1 1 1
3 access_crm_caldav_alarm crm.caldav.alarm model_crm_caldav_alarm 1 1 1 1

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import wizard_cal_import
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
import tools
import wizard
import os
import mx.DateTime
import base64
import pooler
import vobject
class project_cal_imp_wizard(wizard.interface):
form1 = '''<?xml version="1.0"?>
<form string="Import ICS">
<separator string="Select ICS file"/>
<field name="file_path" colspan="4" width="300" nolabel="1"/>
</form>'''
form1_fields = {
'file_path': {
'string': 'Select ICS file',
'type': 'binary',
'required': True,
'filters': '*.ics'
}
}
def _process_imp_ics(self, cr, uid, data, context=None):
task_obj = pooler.get_pool(cr.dbname).get('project.task')
task_obj.import_cal(cr, uid, data['ids'], data, context)
return {}
states = {
'init': {
'actions': [],
'result': {'type': 'form', 'arch':form1, 'fields':form1_fields, \
'state': [('end', '_Cancel', 'gtk-cancel'), ('open', '_Import', 'gtk-ok')]}
},
'open': {
'actions': [],
'result': {'type': 'action', 'action':_process_imp_ics, 'state':'end'}
}
}
project_cal_imp_wizard('caldav.project.import')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: