bzr revid: hmo@tinyerp.com-20101015131459-k969h71ybafv5kib
This commit is contained in:
Harry (OpenERP) 2010-10-15 18:44:59 +05:30
commit 824042bb1c
43 changed files with 2221 additions and 400 deletions

View File

@ -306,7 +306,7 @@ class calendar_attendee(osv.osv):
res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
return [(r['object'], r['name']) for r in res]
def _lang_get(self, cr, uid, context={}):
def _lang_get(self, cr, uid, context=None):
"""
Get language for language selection field.
@param cr: the current row, from the database cursor,
@ -912,10 +912,10 @@ class calendar_event(osv.osv):
_description = "Calendar Event"
__attribute__ = {}
def _tz_get(self, cr, uid, context={}):
def _tz_get(self, cr, uid, context=None):
return [(x.lower(), x) for x in pytz.all_timezones]
def onchange_allday(self, cr, uid, ids, allday, context={}):
def onchange_allday(self, cr, uid, ids, allday, context=None):
"""Sets duration as 24 Hours if event is selcted for all day
@param self: The object pointer
@param cr: the current row, from the database cursor,
@ -1017,7 +1017,8 @@ class calendar_event(osv.osv):
elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
rrule_type = 'custom'
qry = "UPDATE %(table)s set rrule_type=\'%(rule_type)s\' "
qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
qry_args = [ rrule_type, ]
if rrule_type == 'custom':
new_val = val.copy()
@ -1055,17 +1056,12 @@ class calendar_event(osv.osv):
new_val.pop('bymonth')
for k, v in new_val.items():
temp = ", %s='%s'" % (k, v)
qry += temp
qry += ", %s=%%s" % k
qry_args.append(v)
whr = " where id=%(id)s"
qry = qry + whr
val.update({
'table': self._table,
'rule_type': rrule_type,
'id': id,
})
cr.execute(qry % val)
qry = qry + " where id=%s"
qry_args.append(id)
cr.execute(qry, qry_args)
return True
def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
@ -1479,6 +1475,8 @@ true, it will allow you to hide the event alarm information without removing it.
if not real_event_id in new_ids:
new_ids.append(real_event_id)
if vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
vals['vtimezone'] = vals['vtimezone'][40:]
if new_ids:
res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
if (vals.has_key('alarm_id') or vals.has_key('base_calendar_alarm_id'))\
@ -1489,7 +1487,7 @@ true, it will allow you to hide the event alarm information without removing it.
context=context)
return res
def browse(self, cr, uid, ids, context=None, list_class=None, fields_process={}):
def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
"""
Overrides orm browse method.
@param self: the object pointer
@ -1608,6 +1606,8 @@ true, it will allow you to hide the event alarm information without removing it.
"""
if not context:
context = {}
if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
vals['vtimezone'] = vals['vtimezone'][40:]
res = super(calendar_event, self).create(cr, uid, vals, context)
alarm_obj = self.pool.get('res.alarm')
alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
@ -1682,7 +1682,7 @@ class calendar_todo(osv.osv):
@param args: list of tuples of form [(name_of_the_field, operator, value), ...].
@param context: A standard dictionary for contextual values
"""
assert name == 'date'
return self.write(cr, uid, id, { 'date_start': value }, context=context)

View File

@ -1,5 +1,5 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_calendar_attendee","calendar.attendee","model_calendar_attendee","base.group_user",1,1,1,0
"access_calendar_attendee","calendar.attendee","model_calendar_attendee","base.group_user",1,1,1,1
"access_calendar_alarm","calendar.alarm","model_calendar_alarm","base.group_user",1,1,1,1
"access_res_alarm","res.alarm","model_res_alarm","base.group_user",1,1,1,1
"access_calendar_todo","calendar.todo","model_calendar_todo","base.group_user",1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_calendar_attendee calendar.attendee model_calendar_attendee base.group_user 1 1 1 0 1
3 access_calendar_alarm calendar.alarm model_calendar_alarm base.group_user 1 1 1 1
4 access_res_alarm res.alarm model_res_alarm base.group_user 1 1 1 1
5 access_calendar_todo calendar.todo model_calendar_todo base.group_user 1 1 1 1

View File

@ -21,12 +21,12 @@
{
"name" : "Share Calendar using Caldav",
"version" : "1.0",
"name" : "Share Calendar using CalDAV",
"version" : "1.1",
"depends" : [
"base",
"document_webdav",
],
],
'description': """
This module Contains basic functionality for caldav system like:
- Webdav server that provides remote access to calendar
@ -34,7 +34,10 @@
- Customize calendar event and todo attribute with any of OpenERP model
- Provides iCal Import/Export functionality
To access OpenERP Calendar using caldav to remote site use the URL like:
To access Calendars using CalDAV clients, point them to:
http://HOSTNAME:PORT/webdav/DATABASE_NAME/calendars/users/USERNAME/c
To access OpenERP Calendar using WebCal to remote site use the URL like:
http://HOSTNAME:PORT/webdav/DATABASE_NAME/Calendars/CALENDAR_NAME.ics
Where,
@ -53,7 +56,8 @@
'wizard/calendar_event_export_view.xml',
'wizard/calendar_event_import_view.xml',
'wizard/calendar_event_subscribe_view.xml',
'caldav_view.xml'
'caldav_view.xml',
'caldav_setup.xml'
],
"installable" : True,
"active" : False,

View File

@ -20,41 +20,29 @@
##############################################################################
import time
from document import nodes
from document_webdav import nodes
from document.nodes import _str2time
import logging
import StringIO
from orm_utils import get_last_modified
class node_database(nodes.node_database):
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
if not domain:
domain = []
domain2 = domain + [('calendar_collection','=', False)]
res = super(node_database, self)._child_get(cr, name=name, parent_id=parent_id, domain=domain2)
where = [('parent_id','=',parent_id)]
domain2 = domain + [('calendar_collection','=', True)]
if name:
where.append(('name','=',name))
if domain2:
where += domain2
try:
from tools.dict_tools import dict_merge, dict_merge2
except ImportError:
from document.dict_tools import dict_merge, dict_merge2
where2 = where + [('type', '=', 'directory')]
ids = dirobj.search(cr, uid, where2, context=ctx)
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
res.append(node_calendar_collection(dirr.name,self,self.context,dirr))
return res
# TODO: implement DAV-aware errors, inherit from IOError
# Assuming that we have set global properties right, we mark *all*
# directories as having calendar-access.
nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options,
{ 'DAV': ['calendar-access',] })
class node_calendar_collection(nodes.node_dir):
DAV_PROPS = {
"http://calendarserver.org/ns/" : ('getctag',),
}
DAV_M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
}
http_options = { 'DAV': ['calendar-access'] }
DAV_PROPS = dict_merge2(nodes.node_dir.DAV_PROPS,
{ "http://calendarserver.org/ns/" : ('getctag',), } )
DAV_M_NS = dict_merge2(nodes.node_dir.DAV_M_NS,
{ "http://calendarserver.org/ns/" : '_get_dav', } )
def _file_get(self,cr, nodename=False):
return []
@ -80,45 +68,117 @@ class node_calendar_collection(nodes.node_dir):
for cal in fil_obj.browse(cr, uid, ids, context=ctx):
if (not name) or not ext:
res.append(node_calendar(cal.name, self, self.context, cal))
if (not name) or ext:
if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
# these ones must not see the webcal entry.
continue
if cal.has_webcal and (not name) or ext:
res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
# May be both of them!
return res
def _get_dav_owner(self, cr):
# Todo?
return False
def _get_ttag(self, cr):
return 'calen-dir-%d' % self.dir_id
def _get_dav_getctag(self, cr):
result = self.get_etag(cr)
return str(result)
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('collection_id','=',self.dir_id)]
bc_obj = dirobj.pool.get('basic.calendar')
res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
return _str2time(res)
class node_calendar_res_col(nodes.node_res_obj):
""" Calendar collection, as a dynamically created node
This class shall be used instead of node_calendar_collection, when the
node is under dynamic ones.
"""
DAV_PROPS = dict_merge2(nodes.node_res_obj.DAV_PROPS,
{ "http://calendarserver.org/ns/" : ('getctag',), } )
DAV_M_NS = dict_merge2(nodes.node_res_obj.DAV_M_NS,
{ "http://calendarserver.org/ns/" : '_get_dav', } )
def _file_get(self,cr, nodename=False):
return []
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('collection_id','=',self.dir_id)]
ext = False
if name and name.endswith('.ics'):
name = name[:-4]
ext = True
if name:
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 = []
# TODO: shall we use any of our dynamic information??
for cal in fil_obj.browse(cr, uid, ids, context=ctx):
if (not name) or not ext:
res.append(node_calendar(cal.name, self, self.context, cal))
if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
# these ones must not see the webcal entry.
continue
if cal.has_webcal and (not name) or ext:
res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
# May be both of them!
return res
def _get_ttag(self, cr):
return 'calen-dir-%d' % self.dir_id
def _get_dav_getctag(self, cr):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('collection_id','=',self.dir_id)]
bc_obj = dirobj.pool.get('basic.calendar')
res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
return _str2time(res)
class node_calendar(nodes.node_class):
our_type = 'collection'
DAV_PROPS = {
"DAV:": ('principal-collection-set'),
"http://cal.me.com/_namespace/" : ('user-state'),
"http://calendarserver.org/ns/" : (
'dropbox-home-URL',
'notification-URL',
'getctag',),
"DAV:": ('supported-report-set',),
# "http://cal.me.com/_namespace/" : ('user-state',),
"http://calendarserver.org/ns/" : ( 'getctag',),
'http://groupdav.org/': ('resourcetype',),
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-description',
'supported-calendar-component-set',
),
"http://apple.com/ns/ical/": ("calendar-color", "calendar-order"),
}
DAV_PROPS_HIDDEN = {
"urn:ietf:params:xml:ns:caldav" : (
'calendar-data',
'calendar-home-set',
'calendar-user-address-set',
'schedule-inbox-URL',
'schedule-outbox-URL',)}
'calendar-timezone',
'supported-calendar-data',
'max-resource-size',
'min-date-time',
'max-date-time',
)}
DAV_M_NS = {
"DAV:" : '_get_dav',
"http://cal.me.com/_namespace/": '_get_dav',
# "http://cal.me.com/_namespace/": '_get_dav',
'http://groupdav.org/': '_get_gdav',
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
"urn:ietf:params:xml:ns:caldav" : '_get_caldav',
"http://apple.com/ns/ical/": '_get_apple_cal',
}
http_options = { 'DAV': ['calendar-access'] }
@ -131,42 +191,33 @@ class node_calendar(nodes.node_class):
self.content_length = 0
self.displayname = calendar.name
self.cal_type = calendar.type
self.cal_color = calendar.calendar_color or None
self.cal_order = calendar.calendar_order or None
try:
self.uuser = (calendar.user_id and calendar.user_id.login) or 'nobody'
except Exception:
self.uuser = 'nobody'
def _get_dav_getctag(self, cr):
result = self._get_ttag(cr) + ':' + str(time.time())
return str(result)
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
def _get_dav_dropbox_home_URL(self, cr):
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.collection_id.name, calendar.name)
url = urllib.quote('/%s/%s' % (cr.dbname, res))
return url
def _get_dav_notification_URL(self, cr):
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.collection_id.name, calendar.name)
url = urllib.quote('/%s/%s' % (cr.dbname, res))
return url
bc_obj = dirobj.pool.get('basic.calendar')
res = bc_obj.get_cal_max_modified(cr, uid, [self.calendar_id], self, domain=[], context=ctx)
return _str2time(res)
def _get_dav_user_state(self, cr):
#TODO
return True
return 'online'
def get_dav_resourcetype(self, cr):
res = [ ('collection', 'DAV:'),
(str(self.cal_type + '-collection'), 'http://groupdav.org/'),
('calendar', 'urn:ietf:params:xml:ns:caldav') ]
('calendar', 'urn:ietf:params:xml:ns:caldav'),
]
if self.context.get('DAV-client', '') == 'GroupDAV':
res.append((str(self.cal_type + '-collection'), 'http://groupdav.org/'))
return res
def get_domain(self, cr, filters):
@ -174,6 +225,7 @@ class node_calendar(nodes.node_class):
res = []
if not filters:
return res
_log = logging.getLogger('caldav.query')
if filters.localName == 'calendar-query':
res = []
for filter_child in filters.childNodes:
@ -189,26 +241,33 @@ class node_calendar(nodes.node_class):
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')]
if vevent_filter.getAttribute('name'):
res = [('type','=',vevent_filter.getAttribute('name').lower() )]
for cfe in vevent_filter.childNodes:
if cfe.localName == 'time-range':
if cfe.getAttribute('start'):
_log.warning("Ignore start.. ")
# No, it won't work in this API
#val = cfe.getAttribute('start')
#res += [('dtstart','=', cfe)]
elif cfe.getAttribute('end'):
_log.warning("Ignore end.. ")
else:
_log.debug("Unknown comp-filter: %s", cfe.localName)
else:
_log.debug("Unknown comp-filter: %s", vevent_filter.localName)
else:
_log.debug("Unknown filter element: %s", vcalendar_filter.localName)
else:
_log.debug("Unknown calendar-query element: %s", filter_child.localName)
return res
elif filters.localName == 'calendar-multiget':
names = []
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[-2]
if caluri not in names : names.append(caluri)
res = [('name','in',names)]
return res
# this is not the place to process, as it wouldn't support multi-level
# hrefs. So, the code is moved to document_webdav/dav_fs.py
pass
else:
_log.debug("Unknown element in REPORT: %s", filters.localName)
return res
def children(self, cr, domain=None):
@ -227,26 +286,33 @@ class node_calendar(nodes.node_class):
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = []
bc_obj = dirobj.pool.get('basic.calendar')
if name:
if name.endswith('.ics'):
name = name[:-4]
try:
where.append(('id','=',int(name)))
if name.isdigit():
where.append(('id','=',int(name)))
else:
bca_obj = dirobj.pool.get('basic.calendar.alias')
bc_alias = bca_obj.search(cr, uid,
[('cal_line_id.calendar_id', '=', self.calendar_id),
('name', '=', name)] )
if not bc_alias:
return []
bc_val = bca_obj.read(cr, uid, bc_alias, ['res_id',])
where.append(('id', '=', bc_val[0]['res_id']))
except ValueError:
# if somebody requests any other name than the ones we
# generate (non-numeric), it just won't exist
# FIXME: however, this confuses Evolution (at least), which
# thinks the .ics node hadn't been saved.
return []
if not domain:
domain = []
fil_obj = dirobj.pool.get('basic.calendar')
ids = fil_obj.search(cr, uid, domain)
res = []
if self.calendar_id in ids:
res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
# we /could/ be supplying an invalid calendar id to bc_obj, it has to check
res = bc_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
return res
def create_child(self, cr, path, data):
@ -268,7 +334,24 @@ class node_calendar(nodes.node_class):
assert isinstance(res[0], (int, long))
fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
domain=[('id','=',res[0])], context=ctx)
if self.context.get('DAV-client','') in ('iPhone', 'iCalendar',):
# For those buggy clients, register the alias
bca_obj = fil_obj.pool.get('basic.calendar.alias')
ourcal = fil_obj.browse(cr, uid, self.calendar_id)
line_id = None
for line in ourcal.line_ids:
if line.name == ourcal.type:
line_id = line.id
break
assert line_id, "Calendar #%d must have at least one %s line" % \
(ourcal.id, ourcal.type)
if path.endswith('.ics'):
path = path[:-4]
bca_obj.create(cr, uid, { 'cal_line_id': line_id,
'res_id': res[0], 'name': path}, context=ctx)
return fnodes[0]
# If we reach this line, it means that we couldn't import any useful
# (and matching type vs. our node kind) data from the iCal content.
return None
@ -287,8 +370,12 @@ class node_calendar(nodes.node_class):
def rmcol(self, cr):
return False
def _get_caldav_calendar_data(self, cr):
if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
# Never return collective data to iClients, they get confused
# because they do propfind on the calendar node with Depth=1
# and only expect the childrens' data
return None
res = []
for child in self.children(cr):
res.append(child._get_caldav_calendar_data(cr))
@ -299,86 +386,54 @@ class node_calendar(nodes.node_class):
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
return calendar.description
try:
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
return calendar.description or calendar.name
except Exception, e:
return None
def _get_dav_principal_collection_set(self, cr):
import xml
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.collection_id.name, calendar.name)
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
def _get_dav_supported_report_set(self, cr):
return ('supported-report', 'DAV:',
('report','DAV:',
('principal-match','DAV:')
)
)
def _get_caldav_calendar_home_set(self, cr):
import xml.dom.minidom
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
def _get_caldav_supported_calendar_component_set(self, cr):
return ('comp', 'urn:ietf:params:xml:ns:caldav', None,
{'name': self.cal_type.upper()} )
def _get_caldav_calendar_timezone(self, cr):
return None #TODO
def _get_caldav_supported_calendar_data(self, cr):
return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None,
{'content-type': "text/calendar", 'version': "2.0" } )
def _get_caldav_max_resource_size(self, cr):
return 65535
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)
return href
def _get_caldav_min_date_time(self, cr):
return "19700101T000000Z"
def _get_caldav_calendar_user_address_set(self, cr):
import xml.dom.minidom
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
user_obj = self.context._dirobj.pool.get('res.users')
user = user_obj.browse(cr, uid, uid, context=ctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
href = doc.documentElement
href.tagName = 'D:href'
huri = doc.createTextNode('MAILTO:' + str(user.email) or str(user.name))
href.appendChild(huri)
return href
def _get_caldav_schedule_outbox_URL(self, cr):
return self._get_caldav_schedule_inbox_URL(cr)
def _get_caldav_schedule_inbox_URL(self, cr):
import xml.dom.minidom
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.collection_id.name, calendar.name)
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
def _get_caldav_max_date_time(self, cr):
return "21001231T235959Z" # I will be dead by then
def _get_apple_cal_calendar_color(self, cr):
return self.cal_color
def _get_apple_cal_calendar_order(self, cr):
return self.cal_order
class res_node_calendar(nodes.node_class):
our_type = 'file'
DAV_PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
"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',)}
)}
DAV_M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
@ -393,7 +448,7 @@ class res_node_calendar(nodes.node_class):
self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
if res_obj:
if not self.calendar_id: self.calendar_id = res_obj.id
pr = res_obj.perm_read()[0]
pr = res_obj.perm_read(context=context, details=False)[0]
self.create_date = pr.get('create_date')
self.write_date = pr.get('write_date') or pr.get('create_date')
self.displayname = res_obj.name
@ -414,6 +469,7 @@ class res_node_calendar(nodes.node_class):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
context = self.context.context.copy()
context.update(self.dctx)
context.update({'model': self.model, 'res_id':self.res_id})
res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
return res
@ -426,8 +482,11 @@ class res_node_calendar(nodes.node_class):
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
context = self.context.context.copy()
context.update(self.dctx)
context.update({'model': self.model, 'res_id':self.res_id})
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
res = calendar_obj.import_cal(cr, uid, data, self.calendar_id)
res = calendar_obj.import_cal(cr, uid, data, self.calendar_id, context=context)
return res
def _get_ttag(self,cr):
@ -438,7 +497,6 @@ class res_node_calendar(nodes.node_class):
res = '%d' % (self.calendar_id)
return res
def rm(self, cr):
uid = self.context.uid
res = False

View File

@ -0,0 +1,88 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- /calendars tree -->
<record id="document_directory_calendars0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="user_id" ref="base.user_root"/>
<field name="company_id" ref="base.main_company"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">calendars</field>
</record>
<record id="document_directory_resources1" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_calendars0"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">resources</field>
</record>
<record id="document_directory_uids0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="user_id" ref="base.user_root"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_calendars0"/>
<field name="type">ressource</field>
<field name="ressource_type_id" ref="base.model_res_users"/>
<field eval="[(6,0,[ref('base.group_system')])]" name="group_ids"/>
<field name="name">__uids__</field>
</record>
<record id="document_directory_users0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="resource_field" ref="base.field_res_users_login"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_calendars0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">ressource</field>
<field name="ressource_type_id" ref="base.model_res_users"/>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">users</field>
</record>
<record id="document_directory_dctx_0" model="document.directory.dctx">
<field model="document.directory" name="dir_id" ref="document_directory_users0"/>
<field name="field">user_id</field>
<field name="expr">res_id</field>
</record>
<record id="document_directory_c0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field name="user_id" ref="base.user_root"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_users0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">c</field>
<field eval="1" name="calendar_collection"/>
</record>
<record id="document_directory_groups1" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_calendars0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">groups</field>
</record>
<record id="document_webdav_dir_property_calendarhomeset0" model="document.webdav.dir.property">
<field model="document.directory" name="dir_id" ref="document_directory_calendars0"/>
<field name="namespace">urn:ietf:params:xml:ns:caldav</field>
<!-- no parent, it is a global property -->
<field name="name">calendar-home-set</field>
<field name="value">('href','DAV:','/%s/%s/calendars/users/%s/c' % ('webdav',dbname,username) )</field>
<field eval="1" name="do_subst"/>
</record>
</data>
</openerp>

View File

@ -74,6 +74,9 @@
<field name="type"/>
<field name="user_id"/>
<field name="collection_id" required="1"/>
<field name="has_webcal" groups="base.group_extended" />
<field name="calendar_color" groups="base.group_extended" />
<field name="calendar_order" groups="base.group_extended" />
<notebook colspan="4">
<page string="Calendar Lines">
<field name="line_ids" mode="form,tree" colspan="4" nolabel="1">

View File

@ -30,7 +30,10 @@ import pytz
import re
import tools
import time
import logging
from caldav_node import res_node_calendar
from orm_utils import get_last_modified
from tools.safe_eval import safe_eval as eval
try:
import vobject
@ -63,12 +66,13 @@ def uid2openobjectid(cr, uidval, oomodel, rdate):
return (False, None)
qry = 'SELECT DISTINCT(id) FROM %s' % model_obj._table
if rdate:
qry += " where recurrent_id='%s'" % (rdate) #TOFIX: sql injection
cr.execute(qry)
qry += " WHERE recurrent_id=%s"
cr.execute(qry, (rdate,))
r_id = cr.fetchone()
if r_id:
return (id, r_id[0])
else:
return (False, None)
cr.execute(qry)
ids = map(lambda x: str(x[0]), cr.fetchall())
if id in ids:
@ -232,6 +236,7 @@ def map_data(cr, uid, obj, context=None):
class CalDAV(object):
__attribute__ = {}
_logger = logging.getLogger('document.caldav')
def ical_set(self, name, value, type):
""" set calendar Attribute
@ -294,6 +299,7 @@ class CalDAV(object):
att_data = []
exdates = []
_server_tzinfo = pytz.timezone(tools.get_server_timezone())
for cal_data in child.getChildren():
if cal_data.name.lower() == 'organizer':
@ -329,8 +335,8 @@ class CalDAV(object):
if cal_data.name.lower() in self.__attribute__:
if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'):
self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value')
date_utc = cal_data.value.astimezone(pytz.utc)
self.ical_set(cal_data.name.lower(), date_utc, 'value')
date_local = cal_data.value.astimezone(_server_tzinfo)
self.ical_set(cal_data.name.lower(), date_local, 'value')
continue
self.ical_set(cal_data.name.lower(), cal_data.value, 'value')
vals = map_data(cr, uid, self, context=context)
@ -576,6 +582,13 @@ class Calendar(CalDAV, osv.osv):
'create_date': fields.datetime('Created Date', readonly=True),
'write_date': fields.datetime('Modifided Date', readonly=True),
'description': fields.text("description"),
'calendar_color': fields.char('Color', size=20, help="For supporting clients, the color of the calendar entries"),
'calendar_order': fields.integer('Order', help="For supporting clients, the order of this folder among the calendars"),
'has_webcal': fields.boolean('WebCal', required=True, help="Also export a <name>.ics entry next to the calendar folder, with WebCal content."),
}
_defaults = {
'has_webcal': False,
}
def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None):
@ -592,7 +605,7 @@ class Calendar(CalDAV, osv.osv):
continue
if line.name in ('valarm', 'attendee'):
continue
line_domain = eval(line.domain or '[]')
line_domain = eval(line.domain or '[]', context)
line_domain += domain
if ctx_res_id:
line_domain += [('id','=',ctx_res_id)]
@ -606,6 +619,32 @@ class Calendar(CalDAV, osv.osv):
node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id)
res.append(node)
return res
def get_cal_max_modified(self, cr, uid, ids, parent=None, domain=None, context=None):
if not context:
context = {}
if not domain:
domain = []
res = None
ctx_res_id = context.get('res_id', None)
ctx_model = context.get('model', None)
for cal in self.browse(cr, uid, ids):
for line in cal.line_ids:
if ctx_model and ctx_model != line.object_id.model:
continue
if line.name in ('valarm', 'attendee'):
continue
line_domain = eval(line.domain or '[]', context)
line_domain += domain
if ctx_res_id:
line_domain += [('id','=',ctx_res_id)]
mod_obj = self.pool.get(line.object_id.model)
max_data = get_last_modified(mod_obj, cr, uid, line_domain, context=context)
if res and res > max_data:
continue
res = max_data
return res
def export_cal(self, cr, uid, ids, vobj='vevent', context=None):
""" Export Calendar
@ -624,7 +663,7 @@ class Calendar(CalDAV, osv.osv):
continue
if line.name in ('valarm', 'attendee'):
continue
domain = eval(line.domain or '[]')
domain = eval(line.domain or '[]', context)
if ctx_res_id:
domain += [('id','=',ctx_res_id)]
mod_obj = self.pool.get(line.object_id.model)
@ -668,6 +707,15 @@ class Calendar(CalDAV, osv.osv):
val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context)
vals.append(val)
objs.append(cal_children[child.name.lower()])
elif child.name.upper() == 'CALSCALE':
if child.value.upper() != 'GREGORIAN':
self._logger.warning('How do I handle %s calendars?',child.value)
elif child.name.upper() in ('PRODID', 'VERSION'):
pass
elif child.name.upper().startswith('X-'):
self._logger.debug("skipping custom node %s", child.name)
else:
self._logger.debug("skipping node %s", child.name)
res = []
for obj_name in list(set(objs)):
@ -728,6 +776,30 @@ line "%s" more than once' % (vals.get('name'))))
basic_calendar_line()
class basic_calendar_alias(osv.osv):
""" Mapping of client filenames to ORM ids of calendar records
Since some clients insist on putting arbitrary filenames on the .ics data
they send us, and they won't respect the redirection "Location:" header,
we have to store those filenames and allow clients to call our calendar
records with them.
Note that adding a column to all tables that would possibly hold calendar-
mapped data won't work. The user is always allowed to specify more
calendars, on any arbitrary ORM object, without need to alter those tables'
data or structure
"""
_name = 'basic.calendar.alias'
_columns = {
'name': fields.char('Filename', size=512, required=True, select=1),
'cal_line_id': fields.many2one('basic.calendar.lines', 'Calendar', required=True,
select=1, help='The calendar/line this mapping applies to'),
'res_id': fields.integer('Res. ID', required=True, select=1),
}
_sql_constraints = [ ('name_cal_uniq', 'UNIQUE(cal_line_id, name)',
_('The same filename cannot apply to two records!')), ]
basic_calendar_alias()
class basic_calendar_attribute(osv.osv):
_name = 'basic.calendar.attributes'

View File

@ -18,9 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
import caldav_node
import logging
class calendar_collection(osv.osv):
_inherit = 'document.directory'
@ -30,7 +32,8 @@ class calendar_collection(osv.osv):
}
_default = {
'calendar_collection' : False,
}
}
def _get_root_calendar_directory(self, cr, uid, context=None):
objid = self.pool.get('ir.model.data')
try:
@ -40,18 +43,25 @@ class calendar_collection(osv.osv):
root_id = objid.read(cr, uid, mid, ['res_id'])['res_id']
root_cal_dir = self.browse(cr,uid, root_id, context=context)
return root_cal_dir.name
except Exception, e:
import netsvc
logger = netsvc.Logger()
logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set root directory for Calendars:'+ str(e))
except Exception:
logger = logging.getLogger('document')
logger.warning('Cannot set root directory for Calendars:', exc_info=True)
return False
return False
def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
""" try to locate the node in uri,
Return a tuple (node_dir, remaining_path)
"""
return (caldav_node.node_database(context=ncontext), uri)
def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
if dbro is None:
dbro = self.browse(cr, uid, ids, context=context)
if dbro.calendar_collection:
if dynamic:
return caldav_node.node_calendar_res_col
else:
return caldav_node.node_calendar_collection
else:
return super(calendar_collection, self).\
get_node_class(cr, uid, ids, dbro=dbro,dynamic=dynamic,
context=context)
def get_description(self, cr, uid, ids, context=None):
#TODO : return description of all calendars

View File

@ -0,0 +1,43 @@
===============================
Discovery of calendar resources
===============================
1. Srv record
--------------
Calendar server and port should be advertised by a DNS _srv record.
Although this is beyond the capabilities of the OpenERP server, an
example setup is listed below:
-- TODO --
DNS -> http://our-host-ip:port/
2. Well-known uris
-------------------
The OpenERP server may have the 'well-known URIs' servlet activated,
which means that it will advertise its main database and the correct
location of the main CalDAV resource.
http://our-host-ip:port/.well-known/caldav -> http://our-host-ip:port/webdav/dbname/calendars/
3. Caldav collection
---------------------
The CalDAV "collection" is not necessarily a calendar or a folder just
containing calendars under it. It is a DAV resource (aka folder) which
has special DAV properties, so that clients are redirected to the right
urls (like per-user calendars etc.).
http://our-host-ip:port/webdav/dbname/calendars/ -> http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/
4. Calendar home for user
--------------------------
There can be one dynamic folder per user, which will in turn contain the calendars
http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/ ->
http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/[Meetings, Tasks]
5. Calendars
--------------
Each calendar will contain the resource nodes:
.../c/Meetings/ -> .../c/Meetings/123.ics
Principal url

View File

@ -0,0 +1,97 @@
==========================
CalDAV with iPhone How-To
==========================
As from OpenERP v6.0, document_webdav v2.2, the iPhone has been thoroughly
tested and supported as a Calendaring client for the OpenERP CalDAV module.
However, keep in mind that OpenERP is not a straightforward calendaring
server, but an ERP application (with more data + structure) which exposes
that data to calendar clients. That said, the full features that would be
accessible through the Gtk or Web OpenERP clients cannot be crammed into
the Calendar clients (such as the iPhone).
OpenERP server Setup
--------------------
Some modules need to be installed at the OpenERP server. These are:
- caldav: Required, has the reference setup and the necessary
underlying code. Will also cause document, document_webdav
to be installed.
- crm_caldav: Optional, will export the CRM Meetings as a calendar.
- project_caldav: Optional, will export project tasks as calendar.
- http_well_known: Optional, experimental. Will ease bootstrapping,
but only when a DNS srv record is also used.
These will also install a reference setup of the folders, ready to go.
The administrator of OpenERP can add more calendars and structure, if
needed.
DNS server setup
------------------
To be documented.
SSL setup
----------
It is highly advisable that you also setup SSL to work for the OpenERP
server. HTTPS is a server-wide feature in OpenERP, which means a
certificate will be set at the openerp-server.conf and will be the same
for XML-RPC, HTTP, WebDAV and CalDAV.
The iPhone also supports secure connections with SSL, although it does
not expect a self-signed certificate (or one that is not verified by
one of the "big" certificate authorities [1] ).
Phone setup
-------------
The iPhone is fairly easy to setup.
IF you need SSL (and your certificate is not a verified one, as usual),
then you first will need to let the iPhone trust that. Follow these
steps:
s1. Open Safari and enter the https location of the OpenERP server:
https://my.server.ip:8071/
(assuming you have the server at "my.server.ip" and the HTTPS port
is the default 8071)
s2. Safari will try to connect and issue a warning about the certificate
used. Inspect the certificate and click "Accept" so that iPhone
now trusts it.
Now, to setup the calendars, you need to:
1. Click on the "Settings" and go to the "Mail, Contacts, Calendars" page.
2. Go to "Add account..."
3. Click on "Other"
4. From the "Calendars" group, select "Add CalDAV Account"
5. Enter the server's name or IP address at the "Server" entry, the
OpenERP username and password at the next ones.
As a description, you can either leave the server's name or
something like "OpenERP calendars".
6. You _will_ get the "Unable to verify account" error message. That is
because our server is not at the port iPhone expects[2]. But no
need to worry, click OK.
7. At the next page, enter the "Advanced Settings" to specify the right
ports and paths
8. If you have SSL, turn the switch on. Note that port will be changed
to 8443.
9. Specify the port for the OpenERP server: 8071 for SSL, 8069 without.
10. Set the "Account URL" to the right path of the OpenERP webdav:
https://my.server.ip:8071/webdav/dbname/calendars
Where "https://my.server.ip:8071" is the protocol, server name
and port as discussed above, "dbname" is the name of the database.
[Note that the default
"https://my.server.ip:8071/principals/users/username" might also
be edited to
"https://my.server.ip:8071/webdav/dbname/principals/users/username" ]
11. Click on Done. The phone will hopefully connect to the OpenERP server
and verify it can use the account.
12. Go to the main menu of the iPhone and enter the Calendar application.
Your OpenERP calendars will be visible inside the selection of the
"Calendars" button.
Note that when creating a new calendar entry, you will have to specify
which calendar it should be saved at.
[1] I remember one guy that made *lots* of money selling his CA business
off, and since then uses this money to create a software monopoly.
[2] This may not happen if the SRV records at DNS and the well-known URIs
are setup right. But we appreciate that a default OpenERP installation will
not have the DNS server of the company's domain configured.

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010 OpenERP SA (www.openerp.com)
#
# 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/>.
#
##############################################################################
if True: # we need this indentation level ;)
def get_last_modified(self, cr, user, args, context=None, access_rights_uid=None):
"""Return the last modification date of objects in 'domain'
This function has similar semantics to orm.search(), apart from the
limit, offset and order arguments, which make no sense here.
It is useful when we want to find if the table (aka set of records)
has any modifications we should update at the client.
"""
if context is None:
context = {}
self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context)
query = self._where_calc(cr, user, args, context=context)
self._apply_ir_rules(cr, user, query, 'read', context=context)
from_clause, where_clause, where_clause_params = query.get_sql()
where_str = where_clause and (" WHERE %s" % where_clause) or ''
cr.execute('SELECT MAX(COALESCE("%s".write_date, "%s".create_date)) FROM ' % (self._table, self._table) +
from_clause + where_str ,
where_clause_params)
res = cr.fetchall()
return res[0][0]
#eof

View File

@ -3,3 +3,4 @@
"access_basic_calendar_attributes","basic.calendar.attributes","model_basic_calendar_attributes","base.group_user",1,1,1,1
"access_basic_calendar_fields","basic.calendar.fields","model_basic_calendar_fields","base.group_user",1,1,1,1
"access_basic_calendar","basic.calendar","model_basic_calendar","base.group_user",1,1,1,1
"access_basic_calendar_alias","basic.calendar.alias","model_basic_calendar_alias","base.group_user",1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_basic_calendar_attributes basic.calendar.attributes model_basic_calendar_attributes base.group_user 1 1 1 1
4 access_basic_calendar_fields basic.calendar.fields model_basic_calendar_fields base.group_user 1 1 1 1
5 access_basic_calendar basic.calendar model_basic_calendar base.group_user 1 1 1 1
6 access_basic_calendar_alias basic.calendar.alias model_basic_calendar_alias base.group_user 1 1 1 1

View File

@ -37,8 +37,11 @@ class calendar_event_export(osv.osv_memory):
"""
Get Default value for Name field.
"""
if not context:
if context is None:
context = {}
else:
context= context.copy()
context['uid'] = uid
model = context.get('model', 'basic.calendar')
model_obj = self.pool.get(model)
res = super(calendar_event_export, self).default_get( cr, uid, fields, context=context)

View File

@ -42,6 +42,11 @@ class calendar_event_import(osv.osv_memory):
@param ids: List of calendar event imports IDs
@return: dictionary of calendar evet import window with Import successful msg.
"""
if context is None:
context = {}
else:
context = context.copy()
context['uid'] = uid
for data in self.read(cr, uid, ids):
model = data.get('model', 'basic.calendar')

View File

@ -44,6 +44,12 @@ class calendar_event_subscribe(osv.osv_memory):
@return: dictionary of calendar evet subscribe window with Import successful msg.
"""
global cnt
if context is None:
context = {}
else:
context = context.copy()
context['uid'] = uid
for data in self.read(cr, uid, ids):
try:
f = urllib.urlopen(data['url_path'])

View File

@ -22,24 +22,24 @@
{
'name': 'Extened Module to Add CalDav future on Meeting',
'version': '1.0',
'version': '1.1',
'category': 'Generic Modules/CRM & SRM',
'description': """
New Futures in Meeting:
* Share meeting with other calendar clients like sunbird
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['caldav', 'crm'
],
'init_xml': [
'crm_caldav_data.xml',
],
New Futures in Meeting:
* Share meeting with other calendar clients like sunbird
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['caldav', 'crm' ],
'init_xml': [
'crm_caldav_data.xml',
'crm_caldav_setup.xml',
],
'update_xml': [],
'demo_xml': [],
'installable': True,
'active': False,
'demo_xml': [],
'installable': True,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -77,7 +77,7 @@ class crm_meeting(osv.osv):
try:
for val in vals:
# Compute value of duration
if 'date_deadline' in val and 'duration' not in val:
if val.get('date_deadline', False) and 'duration' not in val:
start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
diff = end - start

View File

@ -0,0 +1,287 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="basic_calendar_meetings0" model="basic.calendar">
<field eval="1" name="has_webcal"/>
<field name="description">Meetings per user</field>
<field name="calendar_color">#1E25FF</field>
<field model="document.directory" name="collection_id" ref="caldav.document_directory_c0"/>
<field name="type">vevent</field>
<field name="name">Meetings</field>
</record>
<record id="basic_calendar_lines_attendee0" model="basic.calendar.lines">
<field name="calendar_id" ref="basic_calendar_meetings0"/>
<field name="domain">[]</field>
<field name="name">attendee</field>
<field name="object_id" ref="base_calendar.model_calendar_attendee"/>
</record>
<record id="basic_calendar_lines_valarm0" model="basic.calendar.lines">
<field name="calendar_id" ref="basic_calendar_meetings0"/>
<field name="domain">[]</field>
<field name="name">valarm</field>
<field name="object_id" ref="base_calendar.model_calendar_alarm"/>
</record>
<record id="basic_calendar_lines_vevent0" model="basic.calendar.lines">
<field name="calendar_id" ref="basic_calendar_meetings0"/>
<field name="domain">[('user_id','=', dctx_user_id)]</field>
<field name="name">vevent</field>
<field name="object_id" ref="crm.model_crm_meeting"/>
</record>
<record id="basic_calendar_fields_1" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_attendee"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_attendee_ids"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_2" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_duration"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_duration"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_3" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_description"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_description"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_4" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_attach"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_attach"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_5" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_occurs"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_occurs"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_6" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_interval"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_interval"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_7" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_summary"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_name"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_8" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_duration"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_duration"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_9" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_repeat"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_repeat"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_10" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_action"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_action"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_11" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_related"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_related"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_12" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_organizer"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="crm.field_crm_meeting_organizer"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_13" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_uid"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_id"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_14" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_recurrence-id"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_recurrent_id"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_15" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_vtimezone"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_vtimezone"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_16" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_attendee"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="crm.field_crm_meeting_attendee_ids"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_17" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_rrule"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_rrule"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_18" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_dtend"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_date_deadline"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_19" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_valarm"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_base_calendar_alarm_id"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_20" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_location"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_location"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_21" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_exrule"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_exrule"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_22" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_status"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="crm.field_crm_meeting_state"/>
<field name="mapping">{'tentative': 'draft', 'confirmed': 'open', 'cancelled': 'cancel'}</field>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_23" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_exdate"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_exdate"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_24" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_dtstamp"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_date"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_25" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_description"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_description"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_26" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_dtstart"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_date"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_27" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_class"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_class"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_28" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_created"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_create_date"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_29" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_url"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_base_calendar_url"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_30" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_summary"/>
<field name="type_id" ref="basic_calendar_lines_vevent0"/>
<field name="field_id" ref="base_calendar.field_calendar_event_name"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_31" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_cn"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_cn"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_32" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_sent-by"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_sent_by"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_33" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_language"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_language"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_34" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_delegated-from"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_delegated_from"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_35" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_member"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_member"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_36" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_cutype"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_cutype"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_37" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_role"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_role"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_38" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_partstat"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_state"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_39" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_delegated-to"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_delegated_to"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_40" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_dir"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_dir"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_41" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_rsvp"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_rsvp"/>
<field name="fn">field</field>
</record>
</data>
</openerp>

View File

@ -22,7 +22,7 @@
{
'name': 'Integrated Document Management System',
'version': '1.1',
'version': '2.0',
'category': 'Generic Modules/Others',
'description': """This is a complete document management system:
* User Authentication

View File

@ -21,11 +21,11 @@
import logging
import os
import tempfile
from subprocess import Popen, PIPE
class NhException(Exception):
pass
from subprocess import Popen, PIPE
class indexer(object):
""" An indexer knows how to parse the content of some file.

View File

@ -0,0 +1,57 @@
# -*- encoding: utf-8 -*-
# Copyright P. Christeas <p_christ@hol.gr> 2008-2010
# Copyright 2010 OpenERP SA. http://www.openerp.com
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; version 2 of the License.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
###############################################################################
def dict_merge(*dicts):
""" Return a dict with all values of dicts
"""
res = {}
for d in dicts:
res.update(d)
return res
def dict_merge2(*dicts):
""" Return a dict with all values of dicts.
If some key appears twice and contains iterable objects, the values
are merged (instead of overwritten).
"""
res = {}
for d in dicts:
for k in d.keys():
if k in res and isinstance(res[k], (list, tuple)):
res[k] = res[k] + d[k]
elif k in res and isinstance(res[k], dict):
res[k].update(d[k])
else:
res[k] = d[k]
return res
def dict_filter(srcdic, keys, res=None):
''' Return a copy of srcdic that has only keys set.
If any of keys are missing from srcdic, the result won't have them,
either.
@param res If given, result will be updated there, instead of a new dict.
'''
if res is None:
res = {}
for k in keys:
if k in srcdic:
res[k] = srcdic[k]
return res
#eof

View File

@ -28,6 +28,7 @@ import os
import tools
from tools.translate import _
import nodes
import logging
DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['root_path'], 'filestore'))
@ -106,7 +107,7 @@ class document_file(osv.osv):
'parent_id': __get_def_directory
}
_sql_constraints = [
('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
# filename_uniq is not possible in pure SQL
]
def _check_duplication(self, cr, uid, vals, ids=[], op='create'):
name = vals.get('name', False)
@ -259,6 +260,9 @@ class document_file(osv.osv):
r = stor.prepare_unlink(cr, uid, storage_id, f)
if r:
unres.append(r)
else:
logging.getLogger('document').warning("Unlinking attachment #%s %s that has no storage",
f.id, f.name)
res = super(document_file, self).unlink(cr, uid, ids, context)
stor.do_unlink(cr, uid, unres)
return res

View File

@ -125,6 +125,10 @@ class document_directory(osv.osv):
def get_full_path(self, cr, uid, dir_id, context=None):
""" Return the full path to this directory, in a list, root first
"""
if isinstance(dir_id, (tuple, list)):
assert len(dir_id) == 1
dir_id = dir_id[0]
def _parent(dir_id, path):
parent=self.browse(cr, uid, dir_id)
if parent.parent_id and not parent.ressource_parent_type_id:
@ -174,6 +178,39 @@ class document_directory(osv.osv):
return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
"""Retrieve the class of nodes for this directory
This function can be overriden by inherited classes ;)
@param dbro The browse object, if caller already has it
"""
if dbro is None:
dbro = self.browse(cr, uid, ids, context=context)
if dynamic:
assert dbro.type == 'directory'
return nodes.node_res_obj
elif dbro.type == 'directory':
return nodes.node_dir
elif dbro.type == 'ressource':
return nodes.node_res_dir
else:
raise ValueError("dir node for %s type", dbro.type)
def _prepare_context(self, cr, uid, nctx, context):
""" Fill nctx with properties for this database
@param nctx instance of nodes.node_context, to be filled
@param context ORM context (dict) for us
Note that this function is called *without* a list of ids,
it should behave the same for the whole database (based on the
ORM instance of document.directory).
Some databases may override this and attach properties to the
node_context. See WebDAV, CalDAV.
"""
return
def get_dir_permissions(self, cr, uid, ids ):
"""Check what permission user 'uid' has on directory 'id'
"""
@ -215,6 +252,7 @@ class document_directory(osv.osv):
name=directory.name
if not parent_id:
parent_id=directory.parent_id and directory.parent_id.id or False
# TODO fix algo
if not ressource_parent_type_id:
ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
if not ressource_id:
@ -235,8 +273,11 @@ class document_directory(osv.osv):
def create(self, cr, uid, vals, context=None):
if not self._check_duplication(cr, uid, vals):
raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) :
raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
newname = vals.get('name',False)
if newname:
for illeg in ('/', '@', '$', '#'):
if illeg in newname:
raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
return super(document_directory,self).create(cr, uid, vals, context)
# TODO def unlink(...
@ -255,7 +296,7 @@ class document_directory_dctx(osv.osv):
_name = 'document.directory.dctx'
_description = 'Directory Dynamic Context'
_columns = {
'dir_id': fields.many2one('document.directory', 'Directory', required=True),
'dir_id': fields.many2one('document.directory', 'Directory', required=True, ondelete="cascade"),
'field': fields.char('Field', size=20, required=True, select=1, help="The name of the field. Note that the prefix \"dctx_\" will be prepended to what is typed here."),
'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
"You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),

View File

@ -61,16 +61,27 @@ def get_node_context(cr, uid, context):
return node_context(cr, uid, context)
class node_context(object):
""" This is the root node, representing access to some particular
context """
""" This is the root node, representing access to some particular context
A context is a set of persistent data, which may influence the structure
of the nodes. All other transient information during a data query should
be passed down with function arguments.
"""
cached_roots = {}
node_file_class = None
def __init__(self, cr, uid, context=None):
self.dbname = cr.dbname
self.uid = uid
self.context = context
if context is None:
context = {}
context['uid'] = uid
self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
self.node_file_class = node_file
self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
assert self._dirobj
self._dirobj._prepare_context(cr, uid, self, context=context)
self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
def __eq__(self, other):
@ -88,10 +99,14 @@ class node_context(object):
def __ne__(self, other):
return not self.__eq__(other)
def get(self, name, default=None):
return self.context.get(name, default)
def get_uri(self, cr, uri):
""" Although this fn passes back to doc.dir, it is needed since
it is a potential caching point """
it is a potential caching point.
"""
(ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
while duri:
ndir = ndir.child(cr, duri[0])
@ -104,14 +119,10 @@ class node_context(object):
"""Create (or locate) a node for a directory
@param dbro a browse object of document.directory
"""
fullpath = self._dirobj.get_full_path(cr, self.uid, dbro.id, self.context)
if dbro.type == 'directory':
return node_dir(fullpath, None ,self, dbro)
elif dbro.type == 'ressource':
assert dbro.ressource_parent_type_id == False
return node_res_dir(fullpath, None, self, dbro)
else:
raise ValueError("dir node for %s type", dbro.type)
fullpath = dbro.get_full_path(context=self.context)
klass = dbro.get_node_class(dbro, context=self.context)
return klass(fullpath, None ,self, dbro)
def get_file_node(self, cr, fbro):
""" Create or locate a node for a static file
@ -121,7 +132,7 @@ class node_context(object):
if fbro.parent_id:
parent = self.get_dir_node(cr, fbro.parent_id)
return node_file(fbro.name, parent, self, fbro)
return self.node_file_class(fbro.name, parent, self, fbro)
class node_descriptor(object):
@ -266,22 +277,24 @@ class node_class(object):
""" Get a tag, unique per object + modification.
see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
return self._get_ttag(cr) + ':' + self._get_wtag(cr)
return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
return str(_str2time(self.write_date))
return str(_str2time(self.write_date)).replace('.','')
def _get_ttag(self,cr):
def _get_ttag(self, cr):
""" Get a unique tag for this type/id of object.
Must be overriden, so that each node is uniquely identified.
"""
print "node_class.get_ttag()",self
raise NotImplementedError("get_etag stub()")
raise NotImplementedError("get_ttag stub()")
def get_dav_props(self, cr):
""" If this class has special behaviour for GroupDAV etc, export
its capabilities """
# This fn is placed here rather than WebDAV, because we want the
# baseclass methods to apply to all node subclasses
return self.DAV_PROPS or {}
def match_dav_eprop(self, cr, match, ns, prop):
@ -438,22 +451,20 @@ class node_database(node_class):
if not domain:
domain = []
where2 = where + domain + [('type', '=', 'directory')]
where2 = where + domain + ['|', ('type', '=', 'directory'), \
'&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
ids = dirobj.search(cr, uid, where2, context=ctx)
res = []
for dirr in dirobj.browse(cr, uid, ids, context=ctx):
res.append(node_dir(dirr.name, self, self.context,dirr))
where2 = where + domain + [('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
ids = dirobj.search(cr, uid, where2, context=ctx)
for dirr in dirobj.browse(cr, uid, ids, context=ctx):
res.append(node_res_dir(dirr.name, self, self.context, dirr))
klass = dirr.get_node_class(dirr, context=ctx)
res.append(klass(dirr.name, self, self.context,dirr))
fil_obj = dirobj.pool.get('ir.attachment')
ids = fil_obj.search(cr, uid, where, context=ctx)
if ids:
for fil in fil_obj.browse(cr, uid, ids, context=ctx):
res.append(node_file(fil.name, self, self.context, fil))
klass = self.context.node_file_class
res.append(klass(fil.name, self, self.context, fil))
return res
def _file_get(self,cr, nodename=False):
@ -463,9 +474,6 @@ class node_database(node_class):
def _get_ttag(self,cr):
return 'db-%s' % cr.dbname
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
def mkdosname(company_name, default='noname'):
""" convert a string to a dos-like name"""
if not company_name:
@ -492,7 +500,10 @@ class node_dir(node_database):
self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
self.content_length = 0
self.unixperms = 040750
self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
try:
self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
except Exception:
self.uuser = 'nobody'
self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
self.uidperms = dirr.get_dir_permissions()
if dctx:
@ -505,7 +516,7 @@ class node_dir(node_database):
for dfld in dirr.dctx_ids:
try:
self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
except Exception,e:
except Exception:
print "Cannot eval %s" % dfld.expr
print e
pass
@ -670,12 +681,14 @@ class node_dir(node_database):
return ret
class node_res_dir(node_class):
""" A special sibling to node_dir, which does only contain dynamically
""" A folder containing dynamic folders
A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
All folders should be of type node_res_obj and merely behave like
node_dirs (with limited domain).
"""
our_type = 'collection'
res_obj_class = None
def __init__(self, path, parent, context, dirr, dctx=None ):
super(node_res_dir,self).__init__(path, parent, context)
self.dir_id = dirr.id
@ -687,7 +700,10 @@ class node_res_dir(node_class):
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
self.unixperms = 040750
self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
try:
self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
except Exception:
self.uuser = 'nobody'
self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
self.uidperms = dirr.get_dir_permissions()
self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
@ -782,17 +798,15 @@ class node_res_dir(node_class):
continue
# Yes! we can't do better but skip nameless records.
res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
return res
def _get_ttag(self,cr):
return 'rdir-%d' % self.dir_id
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
class node_res_obj(node_class):
""" A special sibling to node_dir, which does only contain dynamically
""" A dynamically created folder.
A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
All folders should be of type node_res_obj and merely behave like
node_dirs (with limited domain).
@ -828,7 +842,7 @@ class node_res_obj(node_class):
for fld,expr in self.dctx_dict.items():
try:
self.dctx[fld] = safe_eval(expr, dc2)
except Exception,e:
except Exception:
print "Cannot eval %s for %s" % (expr, fld)
print e
pass
@ -886,7 +900,8 @@ class node_res_obj(node_class):
return res
def get_dav_props(self, cr):
def get_dav_props_DEPR(self, cr):
# Deprecated! (but document_ics must be cleaned, first)
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
@ -899,7 +914,8 @@ class node_res_obj(node_class):
res['http://groupdav.org/'] = ('resourcetype',)
return res
def get_dav_eprop(self, cr, ns, prop):
def get_dav_eprop_DEPR(self, cr, ns, prop):
# Deprecated!
if ns != 'http://groupdav.org/' or prop != 'resourcetype':
logger.warning("Who asked for %s:%s?" % (ns, prop))
return None
@ -910,6 +926,7 @@ class node_res_obj(node_class):
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr, uid, ids, context=ctx):
# TODO: remove relic of GroupDAV
if content.extension == '.ics': # FIXME: call the content class!
return ('vevent-collection','http://groupdav.org/')
return None
@ -944,20 +961,21 @@ class node_res_obj(node_class):
res_name = getattr(bo, namefield)
if not res_name:
continue
res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
# TODO Revise
klass = directory.get_node_class(directory, dynamic=True, context=ctx)
res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
where2 = where + [('parent_id','=',self.dir_id) ]
ids = dirobj.search(cr, uid, where2, context=ctx)
for dirr in dirobj.browse(cr, uid, ids, context=ctx):
if dirr.type == 'directory':
res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
elif dirr.type == 'ressource':
# child resources can be controlled by properly set dctx
res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
klass = dirr.get_node_class(dirr, context=ctx)
res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
fil_obj = dirobj.pool.get('ir.attachment')
if self.res_find_all:
@ -967,7 +985,8 @@ class node_res_obj(node_class):
ids = fil_obj.search(cr, uid, where3, context=ctx)
if ids:
for fil in fil_obj.browse(cr, uid, ids, context=ctx):
res.append(node_file(fil.name, self, self.context, fil))
klass = self.context.node_file_class
res.append(klass(fil.name, self, self.context, fil))
# Get Child Ressource Directories
@ -979,9 +998,11 @@ class node_res_obj(node_class):
dirids = dirids + dirobj.search(cr,uid, where5)
for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
if dirr.type == 'directory' and not dirr.parent_id:
res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
if dirr.type == 'ressource':
res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
klass = dirr.get_node_class(dirr, context=ctx)
res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
return res
def create_child_collection(self, cr, objname):
@ -993,9 +1014,9 @@ class node_res_obj(node_class):
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
res_obj = dirobj.pool.get(self.context.context['res_model'])
res_obj = dirobj.pool.get(self.res_model)
object2 = res_obj.browse(cr, uid, self.context.context['res_id']) or False
object2 = res_obj.browse(cr, uid, self.res_id) or False
obj = dirobj.browse(cr, uid, self.dir_id)
if obj and (obj.type == 'ressource') and not object2:
@ -1038,7 +1059,8 @@ class node_res_obj(node_class):
fil_id = fil_obj.create(cr, uid, val, context=ctx)
fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
fnode = node_file(path, self, self.context, fil)
klass = self.context.node_file_class
fnode = klass(path, self, self.context, fil)
if data is not None:
fnode.set_data(cr, data, fil)
return fnode
@ -1046,8 +1068,7 @@ class node_res_obj(node_class):
def _get_ttag(self,cr):
return 'rodir-%d-%d' % (self.dir_id, self.res_id)
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
node_res_dir.res_obj_class = node_res_obj
class node_file(node_class):
our_type = 'file'
@ -1069,7 +1090,10 @@ class node_file(node_class):
elif not parent.check_perms('w'):
self.uidperms = 4
self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
try:
self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
except Exception:
self.uuser = 'nobody'
self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
# This only propagates the problem to get_data. Better
@ -1183,9 +1207,6 @@ class node_file(node_class):
def _get_ttag(self,cr):
return 'file-%d' % self.file_id
def get_dav_resourcetype(self, cr):
return ''
def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
if ndir_node and ndir_node.context != self.context:
raise NotImplementedError("Cannot move files between contexts")
@ -1382,3 +1403,5 @@ class nodefd_content(StringIO, node_descriptor):
finally:
cr.close()
StringIO.close(self)
#eof

View File

@ -12,7 +12,7 @@
I create a "Testing" folder where all the test data will go.
-
!record {model: document.directory, id: dir_tests }:
name: 'Testing'
name: 'Testing (will be deleted!)'
parent_id: dir_root
-
I create an attachment into the root folder (w. empty fields, test that
@ -52,7 +52,7 @@
I search the testing folder for attachments.
-
!python {model: ir.attachment}: |
ids = self.search(cr, uid, [('parent_id.name','=', 'Testing'), ('name','=','Test renamed 2')])
ids = self.search(cr, uid, [('parent_id.name','=', 'Testing (will be deleted!)'), ('name','=','Test renamed 2')])
assert ids == [ ref("file_test2") ], ids
-
I create an attachment to a 3rd resource, eg. a res.country
@ -70,7 +70,14 @@
ids = self.search(cr, uid, [('res_model', '=', 'res.country'), ('res_id', '=', ref("base.za"))])
assert ids == [ ref("attach_3rd")], ids
-
I delete the attachment
I delete the attachments
-
!python {model: ir.attachment}: |
self.unlink(cr, uid, [ref('file_test2')])
self.unlink(cr, uid, [ref('attach_3rd')])
-
I delete the tests folder
-
!python {model: document.directory}: |
self.unlink(cr, uid, [ref('dir_tests')])
cr.commit()

View File

@ -363,7 +363,6 @@ class abstracted_fs(object):
"""Remove the specified directory."""
cr, node, rem = datacr
assert node
cr = self.get_node_cr(node)
node.rmcol(cr)
cr.commit()

View File

@ -200,6 +200,11 @@
# TODO move
-
I remove the 'Test-Folder3'
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
ftp.rmd("Test-Folder3")
-
I check that test3.txt is removed.
-
@ -228,3 +233,22 @@
-
I move the 200 files to 'Test-Folder2'
# TODO
-
I delete the 200 files
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2')
# TODO speed
ftp.delete('test3.txt')
for i in range(0, 200):
ftp.delete('test-name%s.txt' %i)
-
I remove the 'Test-Folder2'
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
ftp.rmd("Test-Folder2")

View File

@ -5,10 +5,10 @@
from document_ftp import test_easyftp as te
ftp = te.get_plain_ftp(timeout=1.0)
-
I create a folder called '<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>' in the server.
I create in the server a folder called 'Äïêéìáóôéêüò ÖÜêåëëïò'
-
!record {model: document.directory, id: dir_itests }:
name: '<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'
name: 'Äïêéìáóôéêüò ÖÜêåëëïò'
parent_id: document.dir_root
-
And then I create another folder, under it, through FTP
@ -16,67 +16,77 @@
!python {model: ir.attachment}: |
cr.commit()
from document_ftp import test_easyftp as te
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>')
ftp.mkd("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>")
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò')
ftp.mkd("ÖÜêåëëïò áðü êÜôù")
-
I check that this folder exists at the server
-
!assert {model: document.directory, id: , search: "[('name','=','<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>')]" }:
!assert {model: document.directory, id: , search: "[('name','=','ÖÜêåëëïò áðü êÜôù')]" }:
- parent_id != False
-
I login with FTP and check that '<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>' is there
I login with FTP and check that 'Äïêéìáóôéêüò ÖÜêåëëïò' is there
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>')
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
-
I create a file named '<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>' into that folder
I create a file named 'ÄïêéìÞ' into that folder
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>')
fdata = StringIO('<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> utf-8')
ftp.storbinary('STOR <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.txt', fdata)
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
fdata = StringIO('êåßìåíï ìå utf-8')
ftp.storbinary('STOR ÄïêéìÞ.txt', fdata)
-
I remove the '<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.txt' file
I remove the 'ÄïêéìÞ.txt' file
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>')
ftp.delete('<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.txt')
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
ftp.delete('ÄïêéìÞ.txt')
-
I rename '<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>' into '<27><><EFBFBD><EFBFBD><EFBFBD>'
I rename 'ÖÜêåëëïò áðü êÜôù' into 'Üëëïò'
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>')
ftp.rename("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>", "<22><><EFBFBD><EFBFBD><EFBFBD>")
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò')
ftp.rename("ÖÜêåëëïò áðü êÜôù", "Üëëïò")
-
I place a file 'file <EFBFBD>3' in '<27><><EFBFBD><EFBFBD><EFBFBD>'
I place a file 'file Ö3' in 'Üëëïò'
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD>')
fdata = StringIO('<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>')
ftp.storbinary('STOR file <EFBFBD>3.txt', fdata)
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
fdata = StringIO('êé Üëëï êåßìåíï')
ftp.storbinary('STOR file Ö3.txt', fdata)
-
I rename the file into file+range(1..200) (large filename)
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD>')
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
vuvuzela = 'b'+''.join('z' * 200)+'!'
ftp.rename("file <EFBFBD>3.txt", vuvuzela)
ftp.rename("file Ö3.txt", vuvuzela)
-
I delete the file with the large name
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD>')
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
vuvuzela = 'b'+''.join('z' * 200)+'!'
ftp.delete(vuvuzela)
-
I delete the testing folders
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
from cStringIO import StringIO
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
ftp.rmd('Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
ftp.rmd('Äïêéìáóôéêüò ÖÜêåëëïò')

View File

@ -118,3 +118,10 @@
- |
Bonus Piste:
I create a 'Partner3' under 'all'
-
I delete the Partners Testing folder
-
!python {model: document.directory}: |
self.unlink(cr, uid, [ref('dir_tests2')])
self.unlink(cr, uid, [ref('dir_respart1')])
cr.commit()

View File

@ -21,3 +21,7 @@
import webdav
import webdav_server
import document_webdav
#eof

View File

@ -29,20 +29,23 @@
##############################################################################
{
"name" : "WebDAV server for Document Management",
"version" : "2.0",
"author" : "OpenERP SA",
"category" : "Generic Modules/Others",
"website": "http://www.openerp.com",
"description": """ With this module, the WebDAV server for the documents is activated.
You can then use any compatible browser to remotely see the attachments of OpenObject.
"name" : "WebDAV server for Document Management",
"version" : "2.2",
"author" : "OpenERP SA",
"category" : "Generic Modules/Others",
"website": "http://www.openerp.com",
"description": """ With this module, the WebDAV server for the documents is activated.
You can then use any compatible browser to remotely see the attachments of OpenObject.
After installation, the webDAV server can be controlled by a [webdav] section in the server's config.
After installation, the webDAV server can be controlled by a [webdav] section in the server's config.
""",
"depends" : ["base", "document"],
"init_xml" : [],
"update_xml" : [],
"demo_xml" : [],
"active": False,
"installable": True
"depends" : ["base", "document"],
"init_xml" : [],
"update_xml" : ['security/ir.model.access.csv',
'webdav_view.xml',
'webdav_setup.xml',
],
"demo_xml" : [],
"active": False,
"installable": True
}

View File

@ -26,8 +26,6 @@ import os
import time
from string import joinfields, split, lower
from service import security
import netsvc
import urlparse
@ -37,9 +35,13 @@ from DAV.iface import *
import urllib
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
from document.nodes import node_res_dir, node_res_obj
from cache import memoize
from tools import misc
try:
from tools.dict_tools import dict_merge2
except ImportError:
from document.dict_tools import dict_merge2
CACHE_SIZE=20000
#hack for urlparse: add webdav in the net protocols
@ -96,8 +98,7 @@ class openerp_dav_handler(dav_interface):
return props
node = self.uri2object(cr, uid, pool, uri2)
if node:
props = props.copy()
props.update(node.get_dav_props(cr))
props = dict_merge2(props, node.get_dav_props(cr))
cr.close()
return props
@ -134,11 +135,11 @@ class openerp_dav_handler(dav_interface):
self.parent.log_message("Exc: %s",traceback.format_exc())
raise default_exc("Operation failed")
def _get_dav_lockdiscovery(self, uri):
raise DAV_NotFound
#def _get_dav_lockdiscovery(self, uri):
# raise DAV_NotFound
def _get_dav_supportedlock(self, uri):
raise DAV_NotFound
#def A_get_dav_supportedlock(self, uri):
# raise DAV_NotFound
def match_prop(self, uri, match, ns, propname):
if self.M_NS.has_key(ns):
@ -186,6 +187,16 @@ class openerp_dav_handler(dav_interface):
cr.close()
return ret
def reduce_useragent(self):
ua = self.parent.headers.get('User-Agent', False)
ctx = {}
if ua:
if 'iPhone' in ua:
ctx['DAV-client'] = 'iPhone'
elif 'Konqueror' in ua:
ctx['DAV-client'] = 'GroupDAV'
return ctx
def get_prop(self, uri, ns, propname):
""" return the value of a given property
@ -194,17 +205,23 @@ class openerp_dav_handler(dav_interface):
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self, uri, ns, propname)
try:
# if it's not in the interface class, a "DAV:" property
# may be at the node class. So shouldn't give up early.
return dav_interface.get_prop(self, uri, ns, propname)
except DAV_NotFound:
pass
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
try:
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound
res = node.get_dav_eprop(cr, ns, propname)
finally:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr, ns, propname)
cr.close()
return res
def get_db(self, uri, rest_ret=False, allow_last=False):
@ -283,26 +300,55 @@ class openerp_dav_handler(dav_interface):
result = []
node = self.uri2object(cr, uid, pool, uri2[:])
if not node:
if cr: cr.close()
raise DAV_NotFound2(uri2)
else:
fp = node.full_path()
if fp and len(fp):
self.parent.log_message('childs: @%s' % fp)
fp = '/'.join(fp)
try:
if not node:
raise DAV_NotFound2(uri2)
else:
fp = None
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) )
fp = node.full_path()
if fp and len(fp):
fp = '/'.join(fp)
self.parent.log_message('childs for: %s' % fp)
else:
result.append( self.urijoin(dbname,d.path) )
if cr: cr.close()
fp = None
domain = None
if filters:
domain = node.get_domain(cr, filters)
if hasattr(filters, 'getElementsByTagNameNS'):
hrefs = filters.getElementsByTagNameNS('DAV:', 'href')
if hrefs:
ul = self.parent.davpath + self.uri2local(uri)
for hr in hrefs:
turi = ''
for tx in hr.childNodes:
if tx.nodeType == hr.TEXT_NODE:
turi += tx.data
if not turi.startswith('/'):
# it may be an absolute URL, decode to the
# relative part, because ul is relative, anyway
uparts=urlparse.urlparse(turi)
turi=uparts[2]
if turi.startswith(ul):
result.append( turi[len(self.parent.davpath):])
else:
self.parent.log_error("ignore href %s because it is not under request path %s", turi, ul)
return result
# We don't want to continue with the children found below
# Note the exceptions and that 'finally' will close the
# cursor
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) )
except DAV_Error:
raise
except Exception, e:
self.parent.log_error("cannot get_childs: "+ str(e))
raise
finally:
if cr: cr.close()
return result
def uri2local(self, uri):
@ -338,7 +384,8 @@ class openerp_dav_handler(dav_interface):
def uri2object(self, cr, uid, pool, uri):
if not uid:
return None
return pool.get('document.directory').get_object(cr, uid, uri)
context = self.reduce_useragent()
return pool.get('document.directory').get_object(cr, uid, uri, context=context)
def get_data(self,uri, rrange=None):
self.parent.log_message('GET: %s' % uri)

View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 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 osv import osv, fields
import nodes
from tools import config
class document_davdir(osv.osv):
_inherit = 'document.directory'
_columns = {
# Placed here just for a reference
'dav_prop_ids': fields.one2many('document.webdav.dir.property', 'dir_id', 'DAV properties'),
}
def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
# Note: in this function, nodes come from document_webdav/nodes.py !
if dbro is None:
dbro = self.browse(cr, uid, ids, context=context)
if dynamic:
assert dbro.type == 'directory'
return nodes.node_res_obj
elif dbro.type == 'directory':
return nodes.node_dir
elif dbro.type == 'ressource':
return nodes.node_res_dir
else:
raise ValueError("dir node for %s type", dbro.type)
def _prepare_context(self, cr, uid, nctx, context):
nctx.node_file_class = nodes.node_file
# We can fill some more fields, but avoid any expensive function
# that might be not worth preparing.
nctx.extra_ctx['webdav_path'] = '/'+config.get_misc('webdav','vdir','webdav')
usr_obj = self.pool.get('res.users')
res = usr_obj.read(cr, uid, uid, ['login'])
if res:
nctx.extra_ctx['username'] = res['login']
# TODO group
return
def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
""" try to locate the node in uri,
Return a tuple (node_dir, remaining_path)
"""
return (nodes.node_database(context=ncontext), uri)
document_davdir()
class dav_dir_property(osv.osv):
""" Arbitrary WebDAV properties, attached to document.directories.
Some DAV properties have to be settable at directories, depending
on the database directory structure.
Example would be the principal-URL.
There _can_ be properties without a directory, which means that they
globally apply to all the directories (aka. collections) of the
present database.
"""
_name = 'document.webdav.dir.property'
_columns = {
'dir_id': fields.many2one('document.directory', 'Directory', required=False, select=1),
'namespace': fields.char('Namespace', size=127, required=True),
'name': fields.char('Name', size=64, required=True),
'value': fields.text('Value'),
'do_subst': fields.boolean('Substitute', required=True),
}
_defaults = {
'do_subst': False,
}
dav_dir_property()
#eof

View File

@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 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 import nodes
from tools.safe_eval import safe_eval as eval
try:
from tools.dict_tools import dict_filter
except ImportError:
from document.dict_tools import dict_filter
import urllib
class node_acl_mixin(object):
def _get_dav_owner(self, cr):
return self.uuser
def _get_dav_group(self, cr):
return self.ugroup
def _get_dav_supported_privilege_set(self, cr):
return '' # TODO
def _get_dav_current_user_privilege_set(self, cr):
return '' # TODO
def _get_dav_props_hlpr(self, cr, par_class, prop_model,
prop_ref_field, res_id):
""" Helper for dav properties, usable in subclasses
@param par_class The parent class
@param prop_model The name of the orm model holding the properties
@param prop_ref_field The name of the field at prop_model pointing to us
@param res_id the id of self in the corresponing orm table, that should
match prop_model.prop_ref_field
"""
ret = par_class.get_dav_props(self, cr)
if prop_model:
propobj = self.context._dirobj.pool.get(prop_model)
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
# Not really needed because we don't do eval here:
# ctx.update({'uid': uid, 'dbname': self.context.dbname })
# dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
sdomain = [(prop_ref_field, '=', False),]
if res_id:
sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
if prop_ids:
ret = ret.copy()
for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
ret[pbro.namespace] = ret.get(pbro.namespace, ()) + \
(pbro.name,)
# Note that we cannot have properties to conditionally appear
# on the context, yet.
return ret
def _get_dav_eprop_hlpr(self, cr, ns, prop,
par_class, prop_model,
prop_ref_field, res_id):
""" Helper for get dav eprop, usable in subclasses
@param namespace the one to search for
@param name Name to search for
@param par_class The parent class
@param prop_model The name of the orm model holding the properties
@param prop_ref_field The name of the field at prop_model pointing to us
@param res_id the id of self in the corresponing orm table, that should
match prop_model.prop_ref_field
"""
ret = par_class.get_dav_eprop(self, cr, ns, prop)
if ret is not None:
return ret
if prop_model:
propobj = self.context._dirobj.pool.get(prop_model)
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
ctx.update({'uid': uid, 'dbname': self.context.dbname })
ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
sdomain = [(prop_ref_field, '=', False),('namespace', '=', ns), ('name','=', prop)]
if res_id:
sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
if prop_ids:
pbro = propobj.browse(cr, uid, prop_ids[0], context=ctx)
val = pbro.value
if pbro.do_subst:
if val.startswith("('") and val.endswith(")"):
glbls = { 'urlquote': urllib.quote, }
val = eval(val, glbls, ctx)
else:
val = val % ctx
return val
return None
class node_dir(node_acl_mixin, nodes.node_dir):
""" override node_dir and add DAV functionality
"""
DAV_PROPS = { "DAV:": ('owner', 'group',
'supported-privilege-set',
'current-user-privilege-set'),
}
DAV_M_NS = { "DAV:" : '_get_dav',}
http_options = { 'DAV': ['access-control',] }
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir,
'document.webdav.dir.property', 'dir_id', self.dir_id)
def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_dir,
'document.webdav.dir.property', 'dir_id', self.dir_id)
class node_file(node_acl_mixin, nodes.node_file):
DAV_PROPS = { "DAV:": ('owner', 'group',
'supported-privilege-set',
'current-user-privilege-set'),
}
DAV_M_NS = { "DAV:" : '_get_dav',}
http_options = { 'DAV': ['access-control', ] }
pass
def get_dav_resourcetype(self, cr):
return ''
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir,
None, 'file_id', self.file_id)
#'document.webdav.dir.property', 'dir_id', self.dir_id)
#def get_dav_eprop(self, cr, ns, prop):
class node_database(nodes.node_database):
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir,
'document.webdav.dir.property', 'dir_id', False)
def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop,
'document.webdav.dir.property', 'dir_id', False)
class node_res_obj(node_acl_mixin, nodes.node_res_obj):
DAV_PROPS = { "DAV:": ('owner', 'group',
'supported-privilege-set',
'current-user-privilege-set'),
}
DAV_M_NS = { "DAV:" : '_get_dav',}
http_options = { 'DAV': ['access-control',] }
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_res_obj,
'document.webdav.dir.property', 'dir_id', self.dir_id)
def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_obj,
'document.webdav.dir.property', 'dir_id', self.dir_id)
class node_res_dir(node_acl_mixin, nodes.node_res_dir):
DAV_PROPS = { "DAV:": ('owner', 'group',
'supported-privilege-set',
'current-user-privilege-set'),
}
DAV_M_NS = { "DAV:" : '_get_dav',}
http_options = { 'DAV': ['access-control',] }
res_obj_class = node_res_obj
def get_dav_resourcetype(self, cr):
return ('collection', 'DAV:')
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_res_dir,
'document.webdav.dir.property', 'dir_id', self.dir_id)
def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_dir,
'document.webdav.dir.property', 'dir_id', self.dir_id)
# Some copies, so that this module can replace 'from document import nodes'
get_node_context = nodes.get_node_context
node_context = nodes.node_context
node_class = nodes.node_class
node_descriptor = nodes.node_descriptor
#eof

View File

@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_webdav_dir_property_all","webdav.dir.property all","model_document_webdav_dir_property",,1,0,0,0
"access_webdav_dir_property_group_doc_manager","webdav.dir.property document manager","model_document_webdav_dir_property","base.group_system",1,1,1,1
"access_webdav_dir_property_group_system","webdav.dir.property group system","model_document_webdav_dir_property","base.group_system",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_webdav_dir_property_all webdav.dir.property all model_document_webdav_dir_property 1 0 0 0
3 access_webdav_dir_property_group_doc_manager webdav.dir.property document manager model_document_webdav_dir_property base.group_system 1 1 1 1
4 access_webdav_dir_property_group_system webdav.dir.property group system model_document_webdav_dir_property base.group_system 1 1 1 1

View File

@ -22,6 +22,8 @@
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
from xml.dom.minicompat import StringTypes
import urlparse
import urllib
from osv import osv
@ -29,12 +31,28 @@ from osv import osv
try:
from DAV import utils
from DAV.propfind import PROPFIND
from DAV.report import REPORT
except ImportError:
raise osv.except_osv('PyWebDAV Import Error!','Please install PyWebDAV \
from http://code.google.com/p/pywebdav/downloads/detail?name=PyWebDAV-0.9.4.tar.gz&can=2&q=/')
import tools
class Text2(xml.dom.minidom.Text):
def writexml(self, writer, indent="", addindent="", newl=""):
data = "%s%s%s" % (indent, self.data, newl)
data = data.replace("&", "&amp;").replace("<", "&lt;")
data = data.replace(">", "&gt;")
writer.write(data)
def createText2Node(doc, data):
if not isinstance(data, StringTypes):
raise TypeError, "node contents must be a string"
t = Text2()
t.data = data
t.ownerDocument = doc
return t
super_mk_prop_response = PROPFIND.mk_prop_response
def mk_prop_response(self, uri, good_props, bad_props, doc):
@ -48,7 +66,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
re=doc.createElement("D:response")
# append namespaces to response
nsnum=0
namespaces = self.namespaces
namespaces = self.namespaces[:]
if 'DAV:' in namespaces:
namespaces.remove('DAV:')
for nsname in namespaces:
@ -67,6 +85,8 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
tuple ('elem', 'ns', sub-elems) for sub-node with elements
tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with
optional elements and attributes
list, of above tuples
"""
if ns == 'DAV:':
@ -108,13 +128,21 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
ve=doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
if len(v) > 2 and isinstance(v[2], list):
# support nested elements like:
# ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
_prop_elem_child(ve, v[1], v[2], ns_prefix)
if len(v) > 2 and v[2] is not None:
if isinstance(v[2], (list, tuple)):
# support nested elements like:
# ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
_prop_elem_child(ve, v[1], v[2], ns_prefix)
else:
vt=createText2Node(doc,tools.ustr(v[2]))
ve.appendChild(vt)
if len(v) > 3 and v[3]:
assert isinstance(v[3], dict)
for ak, av in v[3].items():
ve.setAttribute(ak, av)
pnode.appendChild(ve)
else:
ve=doc.createTextNode(tools.ustr(v))
ve=createText2Node(doc, tools.ustr(v))
pnode.appendChild(ve)
# write href information
@ -124,7 +152,12 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
fileloc = fileloc.encode('utf-8')
href=doc.createElement("D:href")
davpath = self._dataclass.parent.get_davpath()
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
if uparts[0] and uparts[1]:
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
else:
# When the request has been relative, we don't have enough data to
# reply with absolute url here.
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
huri=doc.createTextNode(hurl)
href.appendChild(huri)
re.appendChild(href)
@ -133,6 +166,10 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
ps=doc.createElement("D:propstat")
if good_props:
re.appendChild(ps)
s=doc.createElement("D:status")
t=doc.createTextNode("HTTP/1.1 200 OK")
s.appendChild(t)
ps.appendChild(s)
gp=doc.createElement("D:prop")
for ns in good_props.keys():
@ -141,15 +178,11 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
else:
ns_prefix="ns"+str(namespaces.index(ns))+":"
for p,v in good_props[ns].items():
if not v:
if v is None:
continue
_prop_child(gp, ns, p, v)
ps.appendChild(gp)
s=doc.createElement("D:status")
t=doc.createTextNode("HTTP/1.1 200 OK")
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
# now write the errors!
@ -159,6 +192,10 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
for ecode in bad_props.keys():
ps=doc.createElement("D:propstat")
re.appendChild(ps)
s=doc.createElement("D:status")
t=doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
ps.appendChild(s)
bp=doc.createElement("D:prop")
ps.appendChild(bp)
@ -172,10 +209,6 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
pe=doc.createElement(ns_prefix+str(p))
bp.appendChild(pe)
s=doc.createElement("D:status")
t=doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
# return the new response element
@ -198,7 +231,12 @@ def mk_propname_response(self,uri,propnames,doc):
fileloc = fileloc.encode('utf-8')
href=doc.createElement("D:href")
davpath = self._dataclass.parent.get_davpath()
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
if uparts[0] and uparts[1]:
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
else:
# When the request has been relative, we don't have enough data to
# reply with absolute url here.
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
huri=doc.createTextNode(hurl)
href.appendChild(huri)
re.appendChild(href)
@ -230,3 +268,18 @@ def mk_propname_response(self,uri,propnames,doc):
PROPFIND.mk_prop_response = mk_prop_response
PROPFIND.mk_propname_response = mk_propname_response
super_create_prop = REPORT.create_prop
def create_prop(self):
try:
if (self.filter is not None) and self._depth == "0":
hrefs = self.filter.getElementsByTagNameNS('DAV:', 'href')
if hrefs:
self._depth = "1"
except Exception:
pass
return super_create_prop(self)
REPORT.create_prop = create_prop
#eof

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="document_webdav_dir_property_nodeclass0" model="document.webdav.dir.property">
<field name="namespace">debug:</field>
<field name="name">node_class</field>
<field name="value">%(node_classname)s</field>
<field eval="1" name="do_subst"/>
</record>
<!--
<record id="document_webdav_dir_property_debug0" model="document.webdav.dir.property">
<field name="namespace">debug:</field>
<field name="name">node_context</field>
<field name="value">%r</field>
<! dash dash This will expose the whole context at the property !>
<field eval="1" name="do_subst"/>
</record>
-->
</data>
</openerp>

View File

@ -57,7 +57,7 @@ def OpenDAVConfig(**kw):
class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
verbose = False
protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1',],
_HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
'PROPFIND', 'PROPPATCH', 'OPTIONS', 'MKCOL',
'DELETE', 'TRACE', 'REPORT', ]
@ -81,7 +81,15 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
def setup(self):
self.davpath = '/'+config.get_misc('webdav','vdir','webdav')
self.baseuri = "http://%s:%d/"% (self.server.server_name, self.server.server_port)
addr, port = self.server.server_name, self.server.server_port
server_proto = getattr(self.server,'proto', 'http').lower()
try:
if hasattr(self.request, 'getsockname'):
addr, port = self.request.getsockname()
except Exception, e:
self.log_error("Cannot calculate own address: %s" , e)
# Too early here to use self.headers
self.baseuri = "%s://%s:%d/"% (server_proto, addr, port)
self.IFACE_CLASS = openerp_dav_handler(self, self.verbose)
def copymove(self, CLASS):
@ -156,8 +164,6 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
etag = None
for match in self.headers['If-Match'].split(','):
if match.startswith('"') and match.endswith('"'):
match = match[1:-1]
if match == '*':
if dc.exists(uri):
test = True
@ -208,8 +214,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
if self.headers.has_key("Content-Type"):
ct=self.headers['Content-Type']
try:
location = dc.put(uri,body,ct)
location = dc.put(uri, body, ct)
except DAV_Error, (ec,dd):
self._logger.warning("Cannot PUT to %s: %s", uri, dd, exc_info=True)
return self.send_status(ec)
headers = {}
@ -264,8 +271,7 @@ try:
handler.debug = config.get_misc('webdav','debug',True)
_dc = { 'verbose' : verbose,
'directory' : directory,
'lockemulation' : False,
'lockemulation' : True,
}
conf = OpenDAVConfig(**_dc)

View File

@ -0,0 +1,99 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<!--This file contains a typical setup of a WebDAV folders' structure,
which conforms to the RFCs and extensions of the protocol.
In OpenERP, this is not enforced by any code inside the server, it
is rather a matter of setting these records in our document configuration.
-->
<!-- /principals tree -->
<record id="document_directory_principals0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="user_id" ref="base.user_root"/>
<field name="company_id" ref="base.main_company"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">principals</field>
</record>
<record id="document_directory_groups0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="user_id" ref="base.user_root"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_principals0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">groups</field>
</record>
<record id="document_directory_resources0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_principals0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">resources</field>
</record>
<record id="document_directory_uids1" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="resource_field" ref="base.field_res_users_login"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_principals0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">ressource</field>
<field name="ressource_type_id" ref="base.model_res_users"/>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">__uids__</field>
</record>
<record id="document_directory_users1" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="user_id" ref="base.user_root"/>
<field name="resource_field" ref="base.field_res_users_login"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_principals0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">ressource</field>
<field name="ressource_type_id" ref="base.model_res_users"/>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">users</field>
</record>
<record id="document_directory_locations0" model="document.directory">
<field name="domain">[]</field>
<field eval="1" name="resource_find_all"/>
<field eval="0" name="ressource_tree"/>
<field name="company_id" ref="base.main_company"/>
<field model="document.directory" name="parent_id" ref="document_directory_principals0"/>
<field name="storage_id" ref="document.storage_default"/>
<field name="type">directory</field>
<field eval="[(6,0,[])]" name="group_ids"/>
<field name="name">locations</field>
</record>
<record id="document_webdav_dir_property_currentuserprincipal0" model="document.webdav.dir.property">
<field name="namespace">DAV:</field>
<field name="name">current-user-principal</field>
<field name="value">('href','DAV:','/%s/%s/principals/users/%s' % ('webdav',dbname, username ) )</field>
<field eval="1" name="do_subst"/>
</record>
<record id="document_webdav_dir_property_principalurl0" model="document.webdav.dir.property">
<field name="namespace">DAV:</field>
<field name="name">principal-URL</field>
<field name="value">('href','DAV:','/%s/%s/principals/users/%s' % ('webdav',dbname, username ) )</field>
<field eval="1" name="do_subst"/>
</record>
</data>
</openerp>

View File

@ -0,0 +1,98 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_dir_props_form">
<field name="name">document.webdav.dir.property.form</field>
<field name="model">document.webdav.dir.property</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Properties">
<field name="namespace"/>
<field name="name"/>
<newline />
<field name="dir_id" />
<field name="do_subst" />
<newline />
<field name="value" colspan="4" />
</form>
</field>
</record>
<record model="ir.ui.view" id="view_dir_props_tree">
<field name="name">document.webdav.dir.property.tree</field>
<field name="model">document.webdav.dir.property</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Properties" toolbar="1">
<field name="dir_id" />
<field name="namespace"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="view_dir_props_filter" model="ir.ui.view">
<field name="name">Search View: Directory DAV properties</field>
<field name="model">document.webdav.dir.property</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Document storage">
<field name="name" />
<field name="namespace" />
<newline/>
<group expand="0" string="Group By..." groups="base.group_extended">
<filter string="Dir" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'dir_id'}"/>
<filter string="Namespace" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'namespace'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_dir_props_form">
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.webdav.dir.property</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_dir_props_filter"/>
</record>
<menuitem
name="DAV properties for folders"
action="action_dir_props_form"
id="menu_dir_props"
groups="base.group_extended"
parent="document.menu_document_management_configuration"/>
<!-- Add the properties to the directory form -->
<record model="ir.ui.view" id="view_document_directory_form1">
<field name="name">document.directory.webdav.inherit</field>
<field name="model">document.directory</field>
<field name="inherit_id" ref="document.view_document_directory_form" />
<field name="type">form</field>
<field name="arch" type="xml">
<page string="Dynamic context" position="after">
<page string="WebDAV properties">
<label string="These properties will be added to WebDAV requests" colspan="4" />
<field name="dav_prop_ids" nolabel="1" colspan="4">
<tree string="Properties">
<field name="namespace"/>
<field name="name"/>
</tree>
<form string="Properties">
<field name="namespace"/>
<field name="name"/>
<newline />
<field name="do_subst" />
<newline />
<field name="value" colspan="4" />
</form>
</field>
</page>
</page>
</field>
</record>
</data>
</openerp>

View File

@ -20,13 +20,13 @@
##############################################################################
{
"name": "Caldav task management",
"version": "1.0",
"name": "CalDAV for task management",
"version": "1.1",
"author": "OpenERP SA",
"category": "Generic Modules/Others",
"description": """ Synchronize between Project task and Caldav Vtodo.""",
"depends": ["project", "caldav", "base_calendar"],
"init_xml": ["project_caldav_data.xml"],
"init_xml": ["project_caldav_data.xml", 'project_caldav_setup.xml', ],
"demo_xml": [],
"update_xml": ["project_caldav_view.xml"],
"active": False,

View File

@ -3,10 +3,10 @@
<data noupdate="1">
<record model="basic.calendar" id="caldav.basic_calendar2">
<field name="name">Tasks</field>
<field name="collection_id" ref="document.dir_calendars"></field>
<field name="type">vtodo</field>
</record>
<field name="name">Tasks</field>
<field name="collection_id" ref="document.dir_calendars"></field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.lines" id="caldav.calendar_lines_todo">
<field name="name">vtodo</field>

View File

@ -0,0 +1,272 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="basic_calendar_tasks0" model="basic.calendar">
<field eval="1" name="has_webcal"/>
<field name="description">Tasks per user</field>
<field name="calendar_color">#FFEF74</field>
<field model="document.directory" name="collection_id" ref="caldav.document_directory_c0"/>
<field name="type">vtodo</field>
<field name="name">Tasks</field>
</record>
<record id="basic_calendar_lines_attendee0" model="basic.calendar.lines">
<field model="basic.calendar" name="calendar_id" ref="basic_calendar_tasks0"/>
<field name="domain">[]</field>
<field name="name">attendee</field>
<field name="object_id" ref="base_calendar.model_calendar_attendee"/>
</record>
<record id="basic_calendar_lines_valarm0" model="basic.calendar.lines">
<field model="basic.calendar" name="calendar_id" ref="basic_calendar_tasks0"/>
<field name="domain">[]</field>
<field name="name">valarm</field>
<field name="object_id" ref="base_calendar.model_calendar_alarm"/>
</record>
<record id="basic_calendar_lines_vtodo0" model="basic.calendar.lines">
<field model="basic.calendar" name="calendar_id" ref="basic_calendar_tasks0"/>
<field name="domain">[('user_id','=', dctx_user_id)]</field>
<field name="name">vtodo</field>
<field name="object_id" ref="project.model_project_task"/>
</record>
<record id="basic_calendar_fields_0" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_status"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="project.field_project_task_state"/>
<field name="mapping">{'needs-action': 'draft', 'completed': 'done', 'in-process': 'open', 'cancelled': 'cancelled'}</field>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_1" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_exdate"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_exdate"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_2" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_attendee"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="project_caldav.field_project_task_attendee_ids"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_3" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_valarm"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_base_calendar_alarm_id"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_4" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_description"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_description"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_5" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_url"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_base_calendar_url"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_6" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_percent"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="project.field_project_task_progress"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_7" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_vtimezone"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_vtimezone"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_8" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_summary"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_name"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_9" model="basic.calendar.fields">
<field name="name" ref="caldav.field_event_priority"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="project.field_project_task_priority"/>
<field name="mapping">{'1': '0', '2': '1', '3': '1','4': '1', '5': '2', '6': '3', '7': '3', '8': '3', '9': '4'}</field>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_10" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_location"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_location"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_11" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_exrule"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_exrule"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_12" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_duration"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="project.field_project_task_planned_hours"/>
<field name="fn">hours</field>
</record>
<record id="basic_calendar_fields_13" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_dtstart"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_date"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_14" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_rrule"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_rrule"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_15" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_class"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_class"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_16" model="basic.calendar.fields">
<field name="name" ref="caldav.field_todo_uid"/>
<field model="basic.calendar.lines" name="type_id" ref="basic_calendar_lines_vtodo0"/>
<field name="field_id" ref="base_calendar.field_calendar_todo_id"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_17" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_attendee"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_attendee_ids"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_18" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_duration"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_duration"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_19" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_description"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_description"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_20" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_attach"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_attach"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_21" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_occurs"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_occurs"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_22" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_interval"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_interval"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_23" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_summary"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_name"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_24" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_duration"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_duration"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_25" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_repeat"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_repeat"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_226" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_action"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_calendar_alarm_action"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_27" model="basic.calendar.fields">
<field name="name" ref="caldav.field_alarm_trigger_related"/>
<field name="type_id" ref="basic_calendar_lines_valarm0"/>
<field name="field_id" ref="base_calendar.field_res_alarm_trigger_related"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_28" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_cn"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_cn"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_29" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_sent-by"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_sent_by"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_30" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_language"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_language"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_31" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_delegated-from"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_delegated_from"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_32" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_member"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_member"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_33" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_cutype"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_cutype"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_34" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_role"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_role"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_35" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_partstat"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_state"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_36" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_delegated-to"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_delegated_to"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_37" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_dir"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_dir"/>
<field name="fn">field</field>
</record>
<record id="basic_calendar_fields_38" model="basic.calendar.fields">
<field name="name" ref="caldav.field_attendee_rsvp"/>
<field name="type_id" ref="basic_calendar_lines_attendee0"/>
<field name="field_id" ref="base_calendar.field_calendar_attendee_rsvp"/>
<field name="fn">field</field>
</record>
</data>
</openerp>