[IMP] caldav: supporting caldav protocall

bzr revid: hmo@tinyerp.com-20100415075809-edzwop5gzzdi80jj
This commit is contained in:
Harry (Open ERP) 2010-04-15 13:28:09 +05:30
parent 013abc7709
commit 7aea663bb6
7 changed files with 228 additions and 257 deletions

View File

@ -21,6 +21,7 @@
import calendar
import calendar_collection
import caldav
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

40
addons/caldav/caldav.py Normal file
View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 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 document_webdav import webdav
import tools
from DAV.propfind import PROPFIND
super_mk_prop_response = webdav.mk_prop_response
def mk_prop_response(self,uri,good_props,bad_props,doc):
res = super_mk_prop_response(self, uri,good_props,bad_props,doc)
uris = uri.split('/')
if uri[-1] in ('Calendars'):
ad = doc.createElement('calendar')
ad.setAttribute('xmlns', 'urn:ietf:params:xml:ns:caldav')
cols = res.getElementsByTagName('D:collection')
if cols:
cols[0].parentNode.appendChild(ad)
#cols[0].parentNode.appendChild(vc)
return res
PROPFIND.mk_prop_response = mk_prop_response

View File

@ -54,16 +54,10 @@ class node_database(nodes.node_database):
class node_calendar_collection(nodes.node_dir):
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-data',
'calendar-home-set',
'calendar-user-address-set',
'schedule-inbox-URL',
'schedule-outbox-URL',)}
}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
}
def get_dav_props(self, cr):
return self.PROPS
@ -89,6 +83,59 @@ class node_calendar_collection(nodes.node_dir):
print "Exception:", e
return None
def _file_get(self,cr, nodename=False):
return []
def get_domain(self, cr, filters):
res = []
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = dirobj.pool.get('basic.calendar')
if not filters:
return res
if filters.localName == 'calendar-query':
res = []
for filter_child in filters.childNodes:
if filter_child.nodeType == filter_child.TEXT_NODE:
continue
if filter_child.localName == 'filter':
for vcalendar_filter in filter_child.childNodes:
if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
continue
if vcalendar_filter.localName == 'comp-filter':
if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
for vevent_filter in vcalendar_filter.childNodes:
if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
continue
if vevent_filter.localName == 'comp-filter':
if vevent_filter.getAttribute('name') == 'VEVENT':
res = [('type','=','vevent')]
if vevent_filter.getAttribute('name') == 'VTODO':
res = [('type','=','vtodo')]
return res
elif filters.localName == 'calendar-multiget':
ids = []
for filter_child in filters.childNodes:
if filter_child.nodeType == filter_child.TEXT_NODE:
continue
if filter_child.localName == 'href':
if not filter_child.firstChild:
continue
uri = filter_child.firstChild.data
caluri = uri.split('/')
if len(caluri):
caluri = caluri[-1]
calendar = calendar_obj.name_search(cr, uid, caluri)
if calendar:
calendar_id, calendar_name = calendar[0]
ids.append(calendar_id)
return [('id', 'in', ids)]
return res
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
@ -99,7 +146,7 @@ class node_calendar_collection(nodes.node_dir):
where.append(('name','=',name))
if not domain:
domain = []
where = where + domain
fil_obj = dirobj.pool.get('basic.calendar')
ids = fil_obj.search(cr,uid,where,context=ctx)
res = []
@ -130,31 +177,110 @@ class node_calendar_collection(nodes.node_dir):
def _get_dav_getctag(self, cr):
result = self.get_etag(cr)
return str(result)
return str(result)
class node_calendar(nodes.node_class):
our_type = 'file'
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-data',
'calendar-home-set',
'calendar-user-address-set',
'schedule-inbox-URL',
'schedule-outbox-URL',)}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
def __init__(self,path, parent, context, calendar):
super(node_calendar,self).__init__(path, parent,context)
self.calendar_id = calendar.id
self.mimetype = 'ics'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
fobj = self.context._dirobj.pool.get('basic.calendar').browse(cr, uid, self.calendar_id, context=self.context.context)
s = StringIO.StringIO(self.get_data(cr, fobj))
s.name = self
return s
def get_dav_props(self, cr):
return self.PROPS
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
propname = propname.replace('-','_')
mname = prefix + "_" + propname
if not hasattr(self, mname):
return None
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def get_data(self, cr, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.export_cal(cr, uid, [self.calendar_id])
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def _get_ttag(self,cr):
return 'calendar-%d' % self.calendar_id
def _get_caldav_calendar_data(self, cr):
res = self.get_data(cr)
def _get_caldav_calendar_description(self, cr):
dirobj = self.context._dirobj
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
ctx = self.context.context.copy()
ctx.update(self.dctx)
ids = [self.dir_id]
res = dirobj.get_description(cr, uid, ids, context=ctx)
return res
def _get_caldav_calendar_data(self, cr):
return self.get_data(cr)
ctx.update(self.dctx)
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
return calendar.description
def _get_caldav_calendar_home_set(self, cr):
import xml.dom.minidom
import urllib
dirobj = self.context._dirobj
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
root_cal_dir = dirobj._get_root_calendar_directory(cr, uid, context=ctx)
huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, root_cal_dir)))
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name)))
href = doc.documentElement
href.tagName = 'D:href'
href.appendChild(huri)
@ -178,14 +304,13 @@ class node_calendar_collection(nodes.node_dir):
def _get_caldav_schedule_inbox_URL(self, cr):
import xml.dom.minidom
import urllib
dirobj = self.context._dirobj
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
ids = [self.dir_id]
res = dirobj.get_schedule_inbox_URL(cr, uid, ids, context=ctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.name, calendar.collection_id.name)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
href = doc.documentElement
href.tagName = 'D:href'
@ -196,67 +321,7 @@ class node_calendar_collection(nodes.node_dir):
def _get_caldav_schedule_outbox_URL(self, cr):
import xml.dom.minidom
import urllib
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
ids = [self.dir_id]
res = dirobj.get_schedule_outbox_URL(cr, uid, ids, context=ctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
href = doc.documentElement
href.tagName = 'D:href'
huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
href.appendChild(huri)
return href
class node_calendar(nodes.node_class):
our_type = 'file'
def __init__(self,path, parent, context, calendar):
super(node_calendar,self).__init__(path, parent,context)
self.calendar_id = calendar.id
self.mimetype = 'ics'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
fobj = self.context._dirobj.pool.get('basic.calendar').browse(cr, uid, self.calendar_id, context=self.context.context)
s = StringIO.StringIO(self.get_data(cr, fobj))
s.name = self
return s
def get_dav_props(self, cr):
res = {}
return res
def get_dav_eprop(self,cr,ns,prop):
return None
def get_data(self, cr, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.export_cal(cr, uid, [self.calendar_id])
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def _get_ttag(self,cr):
return 'calendar-%d' % self.calendar_id
return _get_caldav_schedule_inbox_URL(cr)
def get_etag(self, cr):
@ -270,5 +335,5 @@ class node_calendar(nodes.node_class):
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
return str(wtime)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4

View File

@ -70,7 +70,8 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Calendar">
<field name="name" colspan="4"/>
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="collection_id" required="1"/>
<field name="line_ids" mode="form,tree" colspan="4" nolabel="1">
@ -112,7 +113,8 @@
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Calendars" toolbar="1">
<field name="name"/>
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="create_date"/>
<field name="write_date"/>

View File

@ -431,6 +431,8 @@ class Calendar(CalDAV, osv.osv):
'user_id': fields.many2one('res.users', 'Owner'),
'collection_id': fields.many2one('document.directory', 'Collection', \
required=True),
'type': fields.selection([('vevent', 'Event'), ('vtodo', 'TODO')], \
string="Type", size=64),
'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'),
'create_date': fields.datetime('Created Date'),
'write_date': fields.datetime('Modifided Date'),

View File

@ -109,11 +109,11 @@ class node_class(object):
s.append(self.path)
return s #map(lambda x: '/' +x, s)
def children(self, cr):
def children(self, cr, domain=None):
print "node_class.children()"
return [] #stub
def child(self,cr, name):
def child(self,cr, name, domain=None):
print "node_class.child()"
return None
@ -155,6 +155,9 @@ class node_class(object):
def get_dav_eprop(self,cr,ns,prop):
return None
def get_domain(self, cr, filters):
return []
class node_database(node_class):
""" A node representing the database directory
@ -163,12 +166,12 @@ class node_database(node_class):
def __init__(self, path=[], parent=False, context=None):
super(node_database,self).__init__(path, parent, context)
def children(self,cr):
res = self._child_get(cr) + self._file_get(cr)
def children(self, cr, domain=None):
res = self._child_get(cr, domain=domain) + self._file_get(cr)
return res
def child(self, cr, name):
res = self._child_get(cr,name)
def child(self, cr, name, domain=None):
res = self._child_get(cr, name, domain=None)
if res:
return res[0]
res = self._file_get(cr,name)
@ -264,8 +267,8 @@ class node_dir(node_database):
def _child_get(self, cr, name = None):
return super(node_dir,self)._child_get(cr, name, self.dir_id)
def _child_get(self, cr, name=None, domain=None):
return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
def create_child_collection(self, cr, objname):
object2 = False
@ -359,16 +362,16 @@ class node_res_dir(node_class):
for dfld in dirr.dctx_ids:
self.dctx_dict['dctx_' + dfld.field] = dfld.expr
def children(self,cr):
return self._child_get(cr)
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain)
def child(self,cr, name):
res = self._child_get(cr,name)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
return None
def _child_get(self,cr,name = None):
def _child_get(self,cr, name = None, domain=None):
""" return virtual children of resource, based on the
foreign object.
@ -455,11 +458,11 @@ class node_res_obj(node_class):
else:
self.res_id = res_id
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain) + self._file_get(cr)
def child(self,cr, name):
res = self._child_get(cr,name)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
res = self._file_get(cr,name)
@ -514,7 +517,7 @@ class node_res_obj(node_class):
return ('vevent-collection','http://groupdav.org/')
return None
def _child_get(self,cr,name = None):
def _child_get(self,cr, name=None, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
@ -792,147 +795,3 @@ class node_content(node_class):
def _get_ttag(self,cr):
return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
class old_class():
# the old code, remove..
def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
self.cr = cr
def _file_get(self, nodename=False):
if not self.object:
return []
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
res2 = []
where = []
if self.object2:
where.append( ('res_model','=',self.object2._name) )
where.append( ('res_id','=',self.object2.id) )
else:
where.append( ('parent_id','=',self.object.id) )
where.append( ('res_id','=',False) )
if nodename:
where.append( (fobj._rec_name,'=',nodename) )
for content in self.object.content_ids:
res3 = content._table._file_get(self,nodename,content)
if res3:
res2.extend(res3)
ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
if self.object and self.root and (self.object.type=='ressource'):
ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
res = fobj.browse(self.cr, self.uid, ids, context=self.context)
return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
def get_translation(self,value,lang):
# Must go, it works on arbitrary models and could be ambiguous.
result = value
pool = pooler.get_pool(self.cr.dbname)
translation_ids = pool.get('ir.translation').search(self.cr, self.uid, [('value','=',value),('lang','=',lang),('type','=','model')])
if len(translation_ids):
tran_id = translation_ids[0]
translation = pool.get('ir.translation').read(self.cr, self.uid, tran_id, ['res_id','name'])
res_model,field_name = tuple(translation['name'].split(','))
res_id = translation['res_id']
res = pool.get(res_model).read(self.cr, self.uid, res_id, [field_name])
if res:
result = res[field_name]
return result
def directory_list_for_child(self,nodename,parent=False):
pool = pooler.get_pool(self.cr.dbname)
where = []
if nodename:
nodename = self.get_translation(nodename, self.context['lang'])
where.append(('name','=',nodename))
if (self.object and self.object.type=='directory') or not self.object2:
where.append(('parent_id','=',self.object and self.object.id or False))
else:
where.append(('parent_id','=',False))
if self.object:
where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
else:
where.append(('ressource_parent_type_id','=',False))
ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
if self.object2:
ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
return res
def _child_get(self, nodename=False):
if self.type not in ('collection','database'):
return []
res = self.directory_list_for_child(nodename)
result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
if self.type=='database':
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
vargs = [('parent_id','=',False),('res_id','=',False)]
if nodename:
vargs.append((fobj._rec_name,'=',nodename))
file_ids=fobj.search(self.cr,self.uid,vargs)
res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
if self.type=='collection' and self.object.type=="ressource":
where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
pool = pooler.get_pool(self.cr.dbname)
obj = pool.get(self.object.ressource_type_id.model)
_dirname_field = obj._rec_name
if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
_dirname_field = 'dirname'
name_for = obj._name.split('.')[-1]
if nodename and nodename.find(name_for) == 0 :
id = int(nodename.replace(name_for,''))
where.append(('id','=',id))
elif nodename:
if nodename.find('__') :
nodename=nodename.replace('__','/')
for invalid in INVALID_CHARS:
if nodename.find(INVALID_CHARS[invalid]) :
nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
nodename = self.get_translation(nodename, self.context['lang'])
where.append((_dirname_field,'=',nodename))
if self.object.ressource_tree:
if obj._parent_name in obj.fields_get(self.cr,self.uid):
where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
return result
else :
if self.object2:
return result
else:
if self.object2:
return result
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
for r in res:
if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
r.name = eval('r.'+_dirname_field)
else:
r.name = False
if not r.name:
r.name = name_for + '%d'%r.id
for invalid in INVALID_CHARS:
if r.name.find(invalid) :
r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
if result2:
if self.object.ressource_tree:
result += result2
else:
result = result2
return result
def path_get(self):
path = self.path
if self.path[0]=='/':
path = self.path[1:]
return path

View File

@ -135,8 +135,8 @@ class openerp_dav_handler(dav_interface):
cr.close()
return self.db_name_list
def get_childs(self,uri):
""" return the child objects as self.baseuris for the given URI """
def get_childs(self,uri, filters=None):
""" return the child objects as self.baseuris for the given URI """
self.parent.log_message('get childs: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
@ -158,14 +158,16 @@ class openerp_dav_handler(dav_interface):
fp = '/'.join(fp)
else:
fp = None
for d in node.children(cr):
domain = None
if filters:
domain = node.get_domain(cr, filters)
for d in node.children(cr, domain):
self.parent.log_message('child: %s' % d.path)
if fp:
result.append( self.urijoin(dbname,fp,d.path) )
else:
result.append( self.urijoin(dbname,d.path) )
if cr: cr.close()
return result
def uri2local(self, uri):