commit
824042bb1c
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|
@ -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)
|
||||
|
|
|
@ -42,6 +42,11 @@ class calendar_event_import(osv.osv_memory):
|
|||
@param ids: List of calendar event import’s 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')
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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('Äïêéìáóôéêüò ÖÜêåëëïò')
|
||||
|
|
|
@ -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()
|
|
@ -21,3 +21,7 @@
|
|||
|
||||
import webdav
|
||||
import webdav_server
|
||||
|
||||
import document_webdav
|
||||
|
||||
#eof
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
|
@ -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("&", "&").replace("<", "<")
|
||||
data = data.replace(">", ">")
|
||||
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
|
||||
|
|
|
@ -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>
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue