[MERGE] document Improvement : merge from lp:~hmo-tinyerp/openobject-addons/p_christeas_addons/

bzr revid: hmo@tinyerp.com-20091224093327-h163qsphpqi78y9q
This commit is contained in:
Harry (Open ERP)
2009-12-24 15:03:27 +05:30
36 changed files with 4196 additions and 2727 deletions
+6 -1
View File
@@ -19,7 +19,12 @@
#
##############################################################################
import content_index
import std_index
import document_storage
import document_directory
import directory_content
import directory_report
import document
import ftpserver
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+5 -6
View File
@@ -22,27 +22,26 @@
{
'name': 'Integrated Document Management System',
'version': '1.1',
'version': '1.99',
'category': 'Generic Modules/Others',
'description': """This is a complete document management system:
* FTP Interface
'description': """This is a complete document management system:
* User Authentication
* Document Indexation
""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': ['base', 'process'],
'init_xml': ['document_data.xml', 'document_demo.xml'],
'init_xml': [],
'update_xml': [
'document_view.xml',
'document_data.xml',
'security/document_security.xml',
'security/ir.model.access.csv'
],
'demo_xml': [],
'demo_xml': [ 'document_demo.xml',],
'installable': True,
'active': False,
'certificate': '0070515416461',
'certificate': None,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+183 -41
View File
@@ -18,53 +18,195 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
import os
import StringIO
import odt2txt
import tempfile
#
# This should be the indexer
#
def _to_unicode(s):
try:
return s.decode('utf-8')
except UnicodeError:
# A quick hack: if netsvc is not there, emulate it. Thus, work offline, too
try:
import netsvc
def log(lvl,msg):
netsvc.Logger().notifyChannel("index",lvl,msg)
except:
class netsvc:
LOG_NOTSET = 'notset'
LOG_DEBUG_RPC = 'debug_rpc'
LOG_DEBUG = 'debug'
LOG_DEBUG2 = 'debug2'
LOG_INFO = 'info'
LOG_WARNING = 'warn'
LOG_ERROR = 'error'
LOG_CRITICAL = 'critical'
def log(lvl,msg):
print msg
class NhException(Exception):
pass
from subprocess import Popen, PIPE
class indexer():
""" An indexer knows how to parse the content of some file.
Typically, one indexer should be instantiated per file
type.
Override this class to add more functionality. Note that
you should only override the Content or the File methods
that give an optimal result. """
def _getMimeTypes(self):
""" Return supported mimetypes """
return []
def _getExtensions(self):
return []
def _getDefMime(self,ext):
""" Return a mimetype for this document type, ideally the
closest to the extension ext. """
mts = self._getMimeTypes();
if len (mts):
return mts[0]
return None
def indexContent(self,content,filename=None, realfile = None):
""" Use either content or the real file, to index.
Some parsers will work better with the actual
content, others parse a file easier. Try the
optimal.
"""
res = ''
try:
return s.decode('latin')
except UnicodeError:
if content != None:
return self._doIndexContent(content)
except NhException:
pass
if realfile != None:
try:
return s.encode('ascii')
except UnicodeError:
return s
return self._doIndexFile(realfile)
except NhException:
pass
fp = open(realfile,'rb')
content2 = fp.read()
fp.close()
# The not-handled exception may be raised here
return self._doIndexContent(content2)
# last try, with a tmp file
if content:
try:
fname,ext = filename and os.path.splitext(filename) or ('','')
fd, rfname = tempfile.mkstemp(suffix=ext)
os.write(fd, content)
os.close(fd)
res = self._doIndexFile(rfname)
os.unlink(rfname)
return res
except NhException:
pass
raise NhException('No appropriate method to index file')
def _doIndexContent(self,content):
raise NhException("Content not handled here")
def content_index(content, filename=None, content_type=None):
fname,ext = os.path.splitext(filename)
result = ''
if ext in ('.doc'): #or content_type ?
(stdin,stdout) = os.popen2('antiword -', 'b')
stdin.write(content)
stdin.close()
result = _to_unicode(stdout.read())
elif ext == '.pdf':
file_descriptor, file_name = tempfile.mkstemp(suffix=ext)
os.write(file_descriptor, content)
os.close(file_descriptor)
fp = os.popen('pdftotext -enc UTF-8 -nopgbrk '+file_name+' -', 'r')
result = fp.read()
fp.close()
elif ext in ('.xls','.ods','.odt','.odp'):
s = StringIO.StringIO(content)
o = odt2txt.OpenDocumentTextFile(s)
result = _to_unicode(o.toString())
s.close()
elif ext in ('.txt','.py','.patch','.html','.csv','.xml'):
result = content
#else:
# result = content
return result
def _doIndexFile(self,fpath):
raise NhException("Content not handled here")
def mime_match(mime, mdict):
if mdict.has_key(mime):
return (mime, mdict[mime])
if '/' in mime:
mpat = mime.split('/')[0]+'/*'
if mdict.has_key(mpat):
return (mime, mdict[mpat])
return (None, None)
class contentIndex() :
def __init__(self):
self.mimes = {}
self.exts = {}
def register(self, obj):
f = False
for mime in obj._getMimeTypes():
self.mimes[mime] = obj
f = True
for ext in obj._getExtensions():
self.exts[ext] = obj
f = True
if f:
log(netsvc.LOG_DEBUG, "Register content indexer: %r" % obj)
if not f:
raise Exception("Your indexer should at least suport a mimetype or extension")
def doIndex(self,content, filename=None, content_type=None, realfname = None, debug=False):
fobj = None
fname = None
mime = None
if content_type and self.mimes.has_key(content_type):
mime = content_type
fobj = self.mimes[content_type]
elif filename:
bname,ext = os.path.splitext(filename)
if self.exts.has_key(ext):
fobj = self.exts[ext]
mime = fobj._getDefMime(ext)
if content_type and not fobj:
mime,fobj = mime_match(content_type, self.mimes)
if not fobj:
try:
if realfname :
fname = realfname
else:
bname,ext = os.path.splitext(filename)
fd, fname = tempfile.mkstemp(suffix=ext)
os.write(fd, content)
os.close(fd)
fp = Popen(['file','-b','--mime-type',fname], shell=False, stdout=PIPE).stdout
result = fp.read()
fp.close()
mime2 = result.strip()
log(netsvc.LOG_DEBUG,"File gave us: %s" % mime2)
# Note that the temporary file still exists now.
mime,fobj = mime_match(mime2, self.mimes)
if not mime:
mime = mime2
except Exception, e:
log(netsvc.LOG_WARNING,"Cannot determine mime type: %s" % str(e))
try:
if fobj:
res = (mime, fobj.indexContent(content,filename,fname or realfname) )
else:
log(netsvc.LOG_DEBUG,"Have no object, return (%s, None)" % mime)
res = (mime, None )
except Exception, e:
log(netsvc.LOG_WARNING,"Could not index file, %s" % e)
res = None
# If we created a tmp file, unlink it now
if not realfname and fname:
try:
os.unlink(fname)
except Exception, e:
log(netsvc.LOG_WARNING,"Could not unlink %s, %s" %(fname, e))
return res
cntIndex = contentIndex()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+113
View File
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import netsvc
import os
import nodes
import StringIO
class document_directory_content_type(osv.osv):
_name = 'document.directory.content.type'
_description = 'Directory Content Type'
_columns = {
'name': fields.char('Content Type', size=64, required=True),
'code': fields.char('Extension', size=4),
'active': fields.boolean('Active'),
'mimetype': fields.char('Mime Type',size=32)
}
_defaults = {
'active': lambda *args: 1
}
document_directory_content_type()
class document_directory_content(osv.osv):
_name = 'document.directory.content'
_description = 'Directory Content'
_order = "sequence"
def _extension_get(self, cr, uid, context={}):
cr.execute('select code,name from document_directory_content_type where active')
res = cr.fetchall()
return res
_columns = {
'name': fields.char('Content Name', size=64, required=True),
'sequence': fields.integer('Sequence', size=16),
'prefix': fields.char('Prefix', size=16),
'suffix': fields.char('Suffix', size=16),
'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
'directory_id': fields.many2one('document.directory', 'Directory'),
}
_defaults = {
'extension': lambda *args: '.pdf',
'sequence': lambda *args: 1,
'include_name': lambda *args: 1,
}
def _file_get(self, cr, node, nodename, content, context=None):
""" return the nodes of a <node> parent having a <content> content
The return value MUST be false or a list of node_class objects.
"""
# TODO: respect the context!
model = node.res_model
if content.include_name and not model:
return False
res2 = []
tname = ''
if content.include_name:
content_name = node.displayname or ''
obj = node.context._dirobj.pool.get(model)
if content_name:
tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
else:
tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
if tname.find('/'):
tname=tname.replace('/', '_')
if not nodename:
n = nodes.node_content(tname, node, node.context,content)
res2.append( n)
else:
if nodename == tname:
n = nodes.node_content(tname, node, node.context,content)
n.fill_fields(cr)
res2.append(n)
return res2
def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
return True
def process_read(self, cr, uid, node, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.report_id)
srv = netsvc.Service._services['report.'+report.report_name]
pdf,pdftype = srv.create(cr, uid, [node.context.context['res_id']], {}, {})
return pdf
document_directory_content()
+64
View File
@@ -0,0 +1,64 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
# $Id$
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import os
class ir_action_report_xml(osv.osv):
_name="ir.actions.report.xml"
_inherit ="ir.actions.report.xml"
def _model_get(self, cr, uid, ids, name, arg, context):
res = {}
model_pool = self.pool.get('ir.model')
for data in self.read(cr,uid,ids,['model']):
model = data.get('model',False)
if model:
model_id =model_pool.search(cr,uid,[('model','=',model)])
if model_id:
res[data.get('id')] = model_id[0]
else:
res[data.get('id')] = False
return res
def _model_search(self, cr, uid, obj, name, args):
if not len(args):
return []
model_id= args[0][2]
if not model_id:
return []
model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
report_id = self.search(cr,uid,[('model','=',model)])
if not report_id:
return [('id','=','0')]
return [('id','in',report_id)]
_columns={
'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
}
ir_action_report_xml()
+51 -725
View File
@@ -22,498 +22,19 @@
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import os
import pooler
from content_index import content_index
import netsvc
import StringIO
#import StringIO
import random
import string
from psycopg2 import Binary
from tools import config
#from tools import config
import tools
from tools.translate import _
def random_name():
random.seed()
d = [random.choice(string.ascii_letters) for x in xrange(10) ]
name = "".join(d)
return name
# Unsupported WebDAV Commands:
# label
# search
# checkin
# checkout
# propget
# propset
#
# An object that represent an uri
# path: the uri of the object
# content: the Content it belongs to (_print.pdf)
# type: content or collection
# content: objct = res.partner
# collection: object = directory, object2 = res.partner
# file: objct = ir.attachement
# root: if we are at the first directory of a ressource
#
INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
class node_class(object):
def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
self.cr = cr
self.uid = uid
self.path = path
self.object = object
self.object2 = object2
self.context = context
self.content = content
self.type=type
self.root=root
def _file_get(self, nodename=False):
if not self.object:
return []
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
res2 = []
where = []
if self.object2:
where.append( ('res_model','=',self.object2._name) )
where.append( ('res_id','=',self.object2.id) )
else:
where.append( ('parent_id','=',self.object.id) )
where.append( ('res_id','=',False) )
if nodename:
where.append( (fobj._rec_name,'=',nodename) )
for content in self.object.content_ids:
if self.object2 or not content.include_name:
if content.include_name:
content_name = self.object2.name
obj = pool.get(self.object.ressource_type_id.model)
name_for = obj._name.split('.')[-1]
if content_name and content_name.find(name_for) == 0 :
content_name = content_name.replace(name_for,'')
test_nodename = content_name + (content.suffix or '') + (content.extension or '')
else:
test_nodename = (content.suffix or '') + (content.extension or '')
if test_nodename.find('/'):
test_nodename=test_nodename.replace('/', '_')
path = self.path+'/'+test_nodename
if not nodename:
n = node_class(self.cr, self.uid,path, self.object2, False, context=self.context, content=content, type='content', root=False)
res2.append( n)
else:
if nodename == test_nodename:
n = node_class(self.cr, self.uid, path, self.object2, False, context=self.context, content=content, type='content', root=False)
res2.append(n)
ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
if self.object and self.root and (self.object.type=='ressource'):
ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
res = fobj.browse(self.cr, self.uid, ids, context=self.context)
return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
def get_translation(self,value,lang):
result = value
#TODO : to get translation term
return result
def directory_list_for_child(self,nodename,parent=False):
pool = pooler.get_pool(self.cr.dbname)
where = []
if nodename:
nodename = self.get_translation(nodename, self.context['lang'])
where.append(('name','=',nodename))
if (self.object and self.object.type=='directory') or not self.object2:
where.append(('parent_id','=',self.object and self.object.id or False))
else:
where.append(('parent_id','=',False))
if self.object:
where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
else:
where.append(('ressource_parent_type_id','=',False))
ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
if self.object2:
ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
return res
def _child_get(self, nodename=False):
if self.type not in ('collection','database'):
return []
res = self.directory_list_for_child(nodename)
result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
if self.type=='database':
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
vargs = [('parent_id','=',False),('res_id','=',False)]
if nodename:
vargs.append((fobj._rec_name,'=',nodename))
file_ids=fobj.search(self.cr,self.uid,vargs)
res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
if self.type=='collection' and self.object.type=="ressource":
where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
pool = pooler.get_pool(self.cr.dbname)
obj = pool.get(self.object.ressource_type_id.model)
_dirname_field = obj._rec_name
if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
_dirname_field = 'dirname'
name_for = obj._name.split('.')[-1]
if nodename and nodename.find(name_for) == 0 :
id = int(nodename.replace(name_for,''))
where.append(('id','=',id))
elif nodename:
if nodename.find('__') :
nodename=nodename.replace('__','/')
for invalid in INVALID_CHARS:
if nodename.find(INVALID_CHARS[invalid]) :
nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
nodename = self.get_translation(nodename, self.context['lang'])
where.append((_dirname_field,'=',nodename))
if self.object.ressource_tree:
if obj._parent_name in obj.fields_get(self.cr,self.uid):
where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
return result
else :
if self.object2:
return result
else:
if self.object2:
return result
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
for r in res:
if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
r.name = eval('r.'+_dirname_field)
else:
r.name = False
if not r.name:
r.name = name_for + '%d'%r.id
for invalid in INVALID_CHARS:
if r.name.find(invalid) :
r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
if result2:
if self.object.ressource_tree:
result += result2
else:
result = result2
return result
def children(self):
return self._child_get() + self._file_get()
def child(self, name):
res = self._child_get(name)
if res:
return res[0]
res = self._file_get(name)
if res:
return res[0]
return None
def path_get(self):
path = self.path
if self.path[0]=='/':
path = self.path[1:]
return path
class document_directory(osv.osv):
_name = 'document.directory'
_description = 'Document directory'
_columns = {
'name': fields.char('Name', size=64, required=True, select=1),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'file_type': fields.char('Content Type', size=32),
'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
'user_id': fields.many2one('res.users', 'Owner'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'parent_id': fields.many2one('document.directory', 'Parent Item'),
'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
help="Select an object here and Open ERP will create a mapping for each of these " \
"objects, using the given domain, when browsing through FTP."),
'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
help="If you put an object here, this directory template will appear bellow all of these objects. " \
"Don't put a parent directory if you select a parent model."),
'ressource_id': fields.integer('Resource ID'),
'ressource_tree': fields.boolean('Tree Structure',
help="Check this if you want to use the same tree structure as the object selected in the system."),
}
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'domain': lambda self,cr,uid,ctx: '[]',
'type': lambda *args: 'directory',
'ressource_id': lambda *a: 0
}
_sql_constraints = [
('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !')
]
def get_resource_path(self,cr,uid,dir_id,res_model,res_id):
# this method will be used in process module
# to be need test and Improvement if resource dir has parent resource (link resource)
path=[]
def _parent(dir_id,path):
parent=self.browse(cr,uid,dir_id)
if parent.parent_id and not parent.ressource_parent_type_id:
_parent(parent.parent_id.id,path)
path.append(parent.name)
else:
path.append(parent.name)
return path
directory=self.browse(cr,uid,dir_id)
model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
if directory:
_parent(dir_id,path)
if res_id:
path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
user=self.pool.get('res.users').browse(cr,uid,uid)
return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
return False
def _check_recursion(self, cr, uid, ids):
level = 100
while len(ids):
cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str, ids))+')')
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
if not level:
return False
level -= 1
return True
_constraints = [
(_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
]
def __init__(self, *args, **kwargs):
res = super(document_directory, self).__init__(*args, **kwargs)
self._cache = {}
def onchange_content_id(self, cr, uid, ids, ressource_type_id):
return {}
def _get_childs(self, cr, uid, node, nodename=False, context={}):
where = []
if nodename:
nodename = self.get_translation(nodename, self.context['lang'])
where.append(('name','=',nodename))
if object:
where.append(('parent_id','=',object.id))
ids = self.search(cr, uid, where, context)
return self.browse(cr, uid, ids, context), False
"""
PRE:
uri: of the form "Sales Order/SO001"
PORT:
uri
object: the object.directory or object.directory.content
object2: the other object linked (if object.directory.content)
"""
def get_object(self, cr, uid, uri, context={}):
#TODO : set user's context_lang in context
context.update({'lang':False})
if not uri:
return node_class(cr, uid, '', False, context=context, type='database')
turi = tuple(uri)
if False and (turi in self._cache):
(path, oo, oo2, context, content,type,root) = self._cache[turi]
if oo:
object = self.pool.get(oo[0]).browse(cr, uid, oo[1], context)
else:
object = False
if oo2:
object2 = self.pool.get(oo2[0]).browse(cr, uid, oo2[1], context)
else:
object2 = False
node = node_class(cr, uid, '/', False, context=context, type='database')
return node
node = node_class(cr, uid, '/', False, context=context, type='database')
for path in uri[:]:
if path:
node = node.child(path)
if not node:
return False
oo = node.object and (node.object._name, node.object.id) or False
oo2 = node.object2 and (node.object2._name, node.object2.id) or False
self._cache[turi] = (node.path, oo, oo2, node.context, node.content,node.type,node.root)
return node
def get_childs(self, cr, uid, uri, context={}):
node = self.get_object(cr, uid, uri, context)
if uri:
children = node.children()
else:
children= [node]
result = map(lambda node: node.path_get(), children)
#childs,object2 = self._get_childs(cr, uid, object, False, context)
#result = map(lambda x: urlparse.urljoin(path+'/',x.name), childs)
return result
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default ={}
name = self.read(cr, uid, [id])[0]['name']
default.update({'name': name+ " (copy)"})
return super(document_directory,self).copy(cr,uid,id,default,context)
def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
name=vals.get('name',False)
parent_id=vals.get('parent_id',False)
ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
ressource_id=vals.get('ressource_id',0)
if op=='write':
for directory in self.browse(cr,uid,ids):
if not name:
name=directory.name
if not parent_id:
parent_id=directory.parent_id and directory.parent_id.id or False
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:
ressource_id=directory.ressource_id and directory.ressource_id or 0
res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
if op=='create':
res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
return True
def write(self, cr, uid, ids, vals, context=None):
if not self._check_duplication(cr,uid,vals,ids,op='write'):
raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
return super(document_directory,self).write(cr,uid,ids,vals,context=context)
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!'))
return super(document_directory,self).create(cr, uid, vals, context)
document_directory()
class document_directory_node(osv.osv):
_inherit = 'process.node'
_columns = {
'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
}
document_directory_node()
class document_directory_content_type(osv.osv):
_name = 'document.directory.content.type'
_description = 'Directory Content Type'
_columns = {
'name': fields.char('Content Type', size=64, required=True),
'code': fields.char('Extension', size=4),
'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the directory content type without removing it."),
}
_defaults = {
'active': lambda *args: 1
}
document_directory_content_type()
class document_directory_content(osv.osv):
_name = 'document.directory.content'
_description = 'Directory Content'
_order = "sequence"
def _extension_get(self, cr, uid, context={}):
cr.execute('select code,name from document_directory_content_type where active')
res = cr.fetchall()
return res
_columns = {
'name': fields.char('Content Name', size=64, required=True),
'sequence': fields.integer('Sequence', size=16, help="Gives the sequence order when displaying a list of directory contents."),
'suffix': fields.char('Suffix', size=16),
'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
'directory_id': fields.many2one('document.directory', 'Directory'),
}
_defaults = {
'extension': lambda *args: '.pdf',
'sequence': lambda *args: 1,
'include_name': lambda *args: 1,
}
def process_write_pdf(self, cr, uid, node, context={}):
return True
def process_read_pdf(self, cr, uid, node, context={}):
report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
srv = netsvc.LocalService('report.'+report.report_name)
pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
s = StringIO.StringIO(pdf)
s.name = node
return s
document_directory_content()
class ir_action_report_xml(osv.osv):
_name="ir.actions.report.xml"
_inherit ="ir.actions.report.xml"
def _model_get(self, cr, uid, ids, name, arg, context):
res = {}
model_pool = self.pool.get('ir.model')
for data in self.read(cr,uid,ids,['model']):
model = data.get('model',False)
if model:
model_id =model_pool.search(cr,uid,[('model','=',model)])
if model_id:
res[data.get('id')] = model_id[0]
else:
res[data.get('id')] = False
return res
def _model_search(self, cr, uid, obj, name, args):
if not len(args):
return []
model_id= args[0][2]
if not model_id:
return []
model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
report_id = self.search(cr,uid,[('model','=',model)])
if not report_id:
return [('id','=','0')]
return [('id','in',report_id)]
_columns={
'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
}
ir_action_report_xml()
def create_directory(path):
dir_name = random_name()
path = os.path.join(path,dir_name)
os.makedirs(path)
return dir_name
import nodes
class document_file(osv.osv):
_inherit = 'ir.attachment'
@@ -522,70 +43,49 @@ class document_file(osv.osv):
return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
def _data_get(self, cr, uid, ids, name, arg, context):
fbrl = self.browse(cr,uid,ids,context=context)
nctx = nodes.get_node_context(cr,uid,context)
result = {}
cr.execute('select id,store_fname,link from ir_attachment where id in ('+','.join(map(str, ids))+')')
for id,r,l in cr.fetchall():
try:
value = file(os.path.join(self._get_filestore(cr), r), 'rb').read()
result[id] = base64.encodestring(value)
except:
result[id]=''
# if context.get('bin_size', False):
# result[id] = tools.human_size(result[id])
bin_size = context.get('bin_size', False)
for fbro in fbrl:
fnode = nodes.node_file(None,None,nctx,fbro)
if not bin_size:
data = fnode.get_data(cr,fbro)
result[fbro.id] = base64.encodestring(data or '')
else:
result[fbro.id] = fnode.get_data_len(cr,fbro)
return result
#
# This code can be improved
#
def _data_set(self, cr, uid, id, name, value, args=None, context={}):
def _data_set(self, cr, uid, id, name, value, arg, context):
if not value:
filename = self.browse(cr, uid, id, context).store_fname
try:
os.unlink(os.path.join(self._get_filestore(cr), filename))
except:
pass
cr.execute('update ir_attachment set store_fname=NULL WHERE id=%s', (id,) )
return True
#if (not context) or context.get('store_method','fs')=='fs':
try:
path = self._get_filestore(cr)
if not os.path.isdir(path):
try:
os.makedirs(path)
except:
raise except_orm(_('Permission Denied !'), _('You do not permissions to write on the server side.'))
flag = None
# This can be improved
for dirs in os.listdir(path):
if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
flag = dirs
break
flag = flag or create_directory(path)
filename = random_name()
fname = os.path.join(path, flag, filename)
fp = file(fname,'wb')
v = base64.decodestring(value)
fp.write(v)
filesize = os.stat(fname).st_size
cr.execute('update ir_attachment set store_fname=%s,store_method=%s,file_size=%s where id=%s', (os.path.join(flag,filename),'fs',len(v),id))
return True
except Exception,e :
raise except_orm(_('Error!'), str(e))
fbro = self.browse(cr,uid,id,context=context)
nctx = nodes.get_node_context(cr,uid,context)
fnode = nodes.node_file(None,None,nctx,fbro)
res = fnode.set_data(cr,base64.decodestring(value),fbro)
return res
_columns = {
'user_id': fields.many2one('res.users', 'Owner', select=1),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'parent_id': fields.many2one('document.directory', 'Directory', select=1),
# the directory id now is mandatory. It can still be computed automatically.
'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True),
'file_size': fields.integer('File Size', required=True),
'file_type': fields.char('Content Type', size=32),
# If ir.attachment contained any data before document is installed, preserve
# the data, don't drop the column!
'db_datas': fields.binary('Data',oldname='datas'),
'index_content': fields.text('Indexed Content'),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'store_method': fields.selection([('db','Database'),('fs','Filesystem'),('link','Link')], "Storing Method"),
'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary"),
'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary", nodrop=True),
'store_fname': fields.char('Stored Filename', size=200),
'res_model': fields.char('Attached Model', size=64), #res_model
'res_id': fields.integer('Attached ID'), #res_id
@@ -593,10 +93,15 @@ class document_file(osv.osv):
'title': fields.char('Resource Title',size=64),
}
def __get_def_directory(self,cr,uid, context = None):
dirobj = self.pool.get('document.directory')
return dirobj._get_root_directory(cr,uid,context)
_defaults = {
'user_id': lambda self,cr,uid,ctx:uid,
'file_size': lambda self,cr,uid,ctx:0,
'store_method': lambda *args: 'db'
'store_method': lambda *args: 'db',
'parent_id': __get_def_directory
}
_sql_constraints = [
('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
@@ -638,20 +143,11 @@ class document_file(osv.osv):
raise except_orm(_('ValidateError'), _('File name must be unique!'))
result = super(document_file,self).write(cr,uid,ids,vals,context=context)
cr.commit()
try:
for f in self.browse(cr, uid, ids, context=context):
#if 'datas' not in vals:
# vals['datas']=f.datas
res = content_index(base64.decodestring(vals['datas']), f.datas_fname, f.file_type or None)
super(document_file,self).write(cr, uid, ids, {
'index_content': res
})
cr.commit()
except:
pass
return result
def create(self, cr, uid, vals, context={}):
def create(self, cr, uid, vals, context=None):
if not context:
context = {}
vals['title']=vals['name']
vals['parent_id'] = context.get('parent_id',False) or vals.get('parent_id',False)
if not vals.get('res_id', False) and context.get('default_res_id',False):
@@ -686,200 +182,30 @@ class document_file(osv.osv):
import urllib
datas=base64.encodestring(urllib.urlopen(vals['link']).read())
else:
datas = vals.get('datas',False)
datas=vals.get('datas',False)
vals['file_size']= datas and len(datas) or 0
if not self._check_duplication(cr,uid,vals):
raise except_orm(_('ValidateError'), _('File name must be unique!'))
result = super(document_file,self).create(cr, uid, vals, context)
cr.commit()
try:
res = content_index(base64.decodestring(datas), vals['datas_fname'], vals.get('content_type', None))
super(document_file,self).write(cr, uid, [result], {
'index_content': res,
})
cr.commit()
except:
pass
return result
def unlink(self,cr, uid, ids, context={}):
stor = self.pool.get('document.storage')
unres= []
# We have to do the unlink in 2 stages: prepare a list of actual
# files to be unlinked, update the db (safer to do first, can be
# rolled back) and then unlink the files. The list wouldn't exist
# after we discard the objects
for f in self.browse(cr, uid, ids, context):
#if f.store_method=='fs':
try:
os.unlink(os.path.join(self._get_filestore(cr), f.store_fname))
except:
pass
return super(document_file, self).unlink(cr, uid, ids, context)
# TODO: update the node cache
r = stor.prepare_unlink(cr,uid,f.parent_id.storage_id, f)
if r:
unres.append(r)
res = super(document_file, self).unlink(cr, uid, ids, context)
stor.do_unlink(cr,uid,unres)
return res
document_file()
class document_configuration_wizard(osv.osv_memory):
_name='document.configuration.wizard'
_rec_name = 'Auto Directory configuration'
_columns = {
'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
"Keep localhost if you don't know what to write.", required=True),
'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
'port': fields.char('Server Port', size=5, help="Put here the server port. " \
"Keep 8021 if you don't know what to write.", required=True)
}
def get_ftp_server_address(self, cr, uid, context=None):
default_address = config.get('ftp_server_address', None)
if default_address is None:
default_address = tools.misc.detect_ip_addr()
return default_address
def get_ftp_server_port(self, cr, uid, context=None):
return config.get('ftp_server_port', 8021)
def get_suggested_ftp_server_address(self, cr, uid, context=None):
return tools.misc.detect_ip_addr()
_defaults = {
'host': get_ftp_server_address,
'suggested_host': get_suggested_ftp_server_address,
'port': get_ftp_server_port,
}
def action_cancel(self,cr,uid,ids,conect=None):
return {
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.actions.configuration.wizard',
'type': 'ir.actions.act_window',
'target':'new',
}
def action_config(self, cr, uid, ids, context=None):
conf = self.browse(cr, uid, ids[0], context)
obj=self.pool.get('document.directory')
objid=self.pool.get('ir.model.data')
if self.pool.get('sale.order'):
id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': '[]',
})
aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('document.directory.content').create(cr, uid, {
'name': "Print Order",
'suffix': "_print",
'report_id': aid,
'extension': '.pdf',
'include_name': 1,
'directory_id': id,
})
id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
id = objid.browse(cr, uid, id, context=context).res_id
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': "[('state','=','draft')]",
})
if self.pool.get('product.product'):
id = objid._get_id(cr, uid, 'document', 'dir_product')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
})
if self.pool.get('stock.location'):
aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('document.directory.content').create(cr, uid, {
'name': "Product Stock",
'suffix': "_stock_forecast",
'report_id': aid,
'extension': '.pdf',
'include_name': 1,
'directory_id': id,
})
if self.pool.get('account.analytic.account'):
id = objid._get_id(cr, uid, 'document', 'dir_project')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': '[]',
'ressource_tree': 1
})
aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
config['ftp_server_address'] = conf.host
config['ftp_server_port'] = conf.port
config.save()
return {
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.actions.configuration.wizard',
'type': 'ir.actions.act_window',
'target': 'new',
}
document_configuration_wizard()
class document_configuration_ftpserver_wizard(osv.osv_memory):
_name='document.configuration.ftp_server.wizard'
_rec_name = 'Configure FTP server address'
_columns = {
'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
"Keep localhost if you don't know what to write.", required=True),
'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
'port': fields.char('Server Port', size=5, help="Put here the server port. " \
"Keep 8021 if you don't know what to write.", required=True)
}
def get_ftp_server_address(self, cr, uid, context=None):
default_address = tools.misc.detect_ip_addr()
return config.get('ftp_server_address', default_address)
def get_ftp_server_port(self, cr, uid, context=None):
return config.get('ftp_server_port', '8021')
def get_suggested_ftp_server_address(self, cr, uid, context=None):
return tools.misc.detect_ip_addr()
_defaults = {
'host': get_ftp_server_address,
'suggested_host': get_suggested_ftp_server_address,
'port': get_ftp_server_port,
}
def action_cancel(self,cr,uid,ids,conect=None):
return {}
def action_config(self, cr, uid, ids, context=None):
conf = self.browse(cr, uid, ids[0], context)
obj = self.pool.get('ir.model.data')
aid = obj._get_id(cr, uid, 'document', 'action_document_browse')
aid = obj.browse(cr, uid, aid, context=context).res_id
self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
config['ftp_server_address'] = conf.host
config['ftp_server_port'] = conf.port
config.save()
return {}
document_configuration_ftpserver_wizard()
+103 -1
View File
@@ -1,10 +1,112 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<data noupdate="0">
<record model="document.directory.content.type" id="pdf">
<field name="code">.pdf</field>
<field name="name">PDF Report</field>
<field name="mimetype">application/pdf</field>
</record>
<record model="document.storage" id="storage_default">
<field name="name">Default File storage</field>
<field name="user_id" ref="base.user_admin"/>
</record>
<record model="document.storage" id="storage_db">
<field name="name">DB storage</field>
<field name="user_id" ref="base.user_admin"/>
<field name="type" eval="'db'" />
<field name="path"></field>
</record>
<record model="document.directory" id="dir_root">
<field name="name">Documents</field>
<field name="user_id" ref="base.user_admin"/>
<field name="storage_id" ref="storage_default"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_my_folder">
<field name="name">My Folder</field>
<field name="parent_id" ref="dir_root"/>
<field name="user_id" ref="base.user_admin"/>
<field name="storage_id" ref="storage_default"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_partner_category">
<field name="name">Partners by Category</field>
<field name="parent_id" ref="dir_root"/>
<field name="type">ressource</field>
<field name="ressource_tree">1</field>
<field name="storage_id" ref="storage_default"/>
<field name="ressource_id">0</field>
<field name="ressource_type_id" search="[('model','=','res.partner.category')]" />
<field name="user_id" ref="base.user_admin"/>
</record>
<record model="document.directory" id="dir_partner">
<field name="name">Partners</field>
<field name="type">ressource</field>
<field name="domain">[('category_id','in',[active_id])]</field>
<field name="ressource_type_id" search="[('model','=','res.partner')]" />
<field name="ressource_parent_type_id" search="[('model','=','res.partner.category')]" />
<field name="user_id" ref="base.user_admin"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_personnal_folder">
<field name="name">Personnal Folders</field>
<field name="parent_id" ref="dir_root"/>
<field name="type">ressource</field>
<field name="storage_id" ref="storage_default"/>
<field name="ressource_type_id" ref="base.model_res_users" />
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_product">
<field name="name">Products</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order">
<field name="name">Sales Order</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order_all">
<field name="name">All Sales Order</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_sale_order"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order_quote">
<field name="name">Quotations</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_sale_order"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_project">
<field name="name">Projects</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
</data>
-84
View File
@@ -2,90 +2,6 @@
<openerp>
<data noupdate="1">
<record model="document.directory" id="dir_root">
<field name="name">Documents</field>
<field name="user_id" ref="base.user_admin"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_my_folder">
<field name="name">My Folder</field>
<field name="parent_id" ref="dir_root"/>
<field name="user_id" ref="base.user_admin"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_partner_category">
<field name="name">Partners by Category</field>
<field name="parent_id" ref="dir_root"/>
<field name="type">ressource</field>
<field name="ressource_tree">1</field>
<field name="ressource_id">0</field>
<field name="ressource_type_id" search="[('model','=','res.partner.category')]" />
<field name="user_id" ref="base.user_admin"/>
</record>
<record model="document.directory" id="dir_partner">
<field name="name">Partners</field>
<field name="type">ressource</field>
<field name="domain">[('category_id','in',[active_id])]</field>
<field name="ressource_type_id" search="[('model','=','res.partner')]" />
<field name="ressource_parent_type_id" search="[('model','=','res.partner.category')]" />
<field name="user_id" ref="base.user_admin"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_personnal_folder">
<field name="name">Personnal Folders</field>
<field name="parent_id" ref="dir_root"/>
<field name="type">ressource</field>
<field name="ressource_type_id" ref="base.model_res_users" />
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_product">
<field name="name">Products</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order">
<field name="name">Sales Order</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order_all">
<field name="name">All Sales Order</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_sale_order"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_sale_order_quote">
<field name="name">Quotations</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_sale_order"/>
<field name="ressource_id">0</field>
</record>
<record model="document.directory" id="dir_project">
<field name="name">Projects</field>
<field name="user_id" ref="base.user_admin"/>
<field name="parent_id" ref="dir_root"/>
<field name="ressource_id">0</field>
</record>
</data>
</openerp>
+313
View File
@@ -0,0 +1,313 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import os
import nodes
from tools.translate import _
class document_directory(osv.osv):
_name = 'document.directory'
_description = 'Document directory'
_columns = {
'name': fields.char('Name', size=64, required=True, select=1),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'file_type': fields.char('Content Type', size=32),
'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
'user_id': fields.many2one('res.users', 'Owner'),
'storage_id': fields.many2one('document.storage', 'Storage'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'parent_id': fields.many2one('document.directory', 'Parent Item'),
'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
help="Select an object here and Open ERP will create a mapping for each of these " \
"objects, using the given domain, when browsing through FTP."),
'resource_field': fields.char('Name field',size=32,help='Field to be used as name on resource directories. If empty, the "name" will be used.'),
'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
help="If you put an object here, this directory template will appear bellow all of these objects. " \
"Don't put a parent directory if you select a parent model."),
'ressource_id': fields.integer('Resource ID'),
'ressource_tree': fields.boolean('Tree Structure',
help="Check this if you want to use the same tree structure as the object selected in the system."),
'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'),
}
def _get_root_directory(self, cr,uid, context=None):
objid=self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'dir_root')
if not mid:
return None
except Exception, e:
import netsvc
logger = netsvc.Logger()
logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set directory root:'+ str(e))
return None
return objid.browse(cr, uid, mid, context=context).res_id
def _get_def_storage(self,cr,uid,context=None):
if context and context.has_key('default_parent_id'):
# Use the same storage as the parent..
diro = self.browse(cr,uid,context['default_parent_id'])
if diro.storage_id:
return diro.storage_id.id
objid=self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'storage_default')
return objid.browse(cr, uid, mid, context=context).res_id
except Exception:
return None
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'domain': lambda self,cr,uid,ctx: '[]',
'type': lambda *args: 'directory',
'ressource_id': lambda *a: 0,
'parent_id': _get_root_directory,
'storage_id': _get_def_storage,
}
_sql_constraints = [
('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!')
]
def ol_get_resource_path(self,cr,uid,dir_id,res_model,res_id):
# this method will be used in process module
# to be need test and Improvement if resource dir has parent resource (link resource)
path=[]
def _parent(dir_id,path):
parent=self.browse(cr,uid,dir_id)
if parent.parent_id and not parent.ressource_parent_type_id:
_parent(parent.parent_id.id,path)
path.append(parent.name)
else:
path.append(parent.name)
return path
directory=self.browse(cr,uid,dir_id)
model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
if directory:
_parent(dir_id,path)
path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
#user=self.pool.get('res.users').browse(cr,uid,uid)
#return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
# No way we will return the password!
return "ftp://user:pass@host:port/test/this"
return False
def _check_recursion(self, cr, uid, ids):
level = 100
while len(ids):
cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
if not level:
return False
level -= 1
return True
_constraints = [
(_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
]
def __init__(self, *args, **kwargs):
res = super(document_directory, self).__init__(*args, **kwargs)
#self._cache = {}
def onchange_content_id(self, cr, uid, ids, ressource_type_id):
return {}
"""
PRE:
uri: of the form "Sales Order/SO001"
PORT:
uri
object: the object.directory or object.directory.content
object2: the other object linked (if object.directory.content)
"""
def get_object(self, cr, uid, uri, context=None):
""" Return a node object for the given uri.
This fn merely passes the call to node_context
"""
if not context:
context = {}
lang = context.get('lang',False)
if not lang:
user = self.pool.get('res.users').browse(cr, uid, uid)
lang = user.context_lang
context['lang'] = lang
try: #just instrumentation
return nodes.get_node_context(cr, uid, context).get_uri(cr,uri)
except Exception,e:
print "exception: ",e
raise
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)
"""
did = root_id
duri = uri
path = []
context = ncontext.context
while len(duri):
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
if not nid:
break
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
return (nodes.node_dir(path, nparent,ncontext,self.browse(cr,uid,did, context)), duri)
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','ressource')], context=context)
if nid:
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
return nodes.node_res_dir(path, nparent,ncontext,self.browse(cr,uid,did, context))
# Here, we must find the appropriate non-dir child..
# Chech for files:
fil_obj = self.pool.get('ir.attachment')
nid = fil_obj.search(cr,uid,[('parent_id','=',did),('name','=',duri[0])],context=context)
if nid:
if len(duri)>1:
# cannot treat child as a dir
return None
if len(nid)>1:
print "Duplicate file?",did,duri[0]
path.append(duri[0])
return nodes.node_file(path,nparent,ncontext,fil_obj.browse(cr,uid,nid[0],context))
print "nothing found:",did, duri
#still, nothing found
return None
def old_code():
if not uri:
return node_database(cr, uid, context=context)
turi = tuple(uri)
node = node_class(cr, uid, '/', False, context=context, type='database')
for path in uri[:]:
if path:
node = node.child(path)
if not node:
return False
oo = node.object and (node.object._name, node.object.id) or False
oo2 = node.object2 and (node.object2._name, node.object2.id) or False
return node
def ol_get_childs(self, cr, uid, uri, context={}):
node = self.get_object(cr, uid, uri, context)
if uri:
children = node.children()
else:
children= [node]
result = map(lambda node: node.path_get(), children)
return result
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default ={}
name = self.read(cr, uid, [id])[0]['name']
default.update({'name': name+ " (copy)"})
return super(document_directory,self).copy(cr,uid,id,default,context)
def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
name=vals.get('name',False)
parent_id=vals.get('parent_id',False)
ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
ressource_id=vals.get('ressource_id',0)
if op=='write':
for directory in self.browse(cr,uid,ids):
if not name:
name=directory.name
if not parent_id:
parent_id=directory.parent_id and directory.parent_id.id or False
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:
ressource_id=directory.ressource_id and directory.ressource_id or 0
res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
if op=='create':
res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
return True
def write(self, cr, uid, ids, vals, context=None):
if not self._check_duplication(cr,uid,vals,ids,op='write'):
raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
return super(document_directory,self).write(cr,uid,ids,vals,context=context)
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!'))
return super(document_directory,self).create(cr, uid, vals, context)
document_directory()
class document_directory_dctx(osv.osv):
""" In order to evaluate dynamic folders, child items could have a limiting
domain expression. For that, their parents will export a context where useful
information will be passed on.
If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
children could have a domain like [('sale_id', = ,dctx_s_id )]
This system should be used recursively, that is, parent dynamic context will be
appended to all children down the tree.
"""
_name = 'document.directory.dctx'
_description = 'Directory dynamic context'
_columns = {
'dir_id': fields.many2one('document.directory', 'Directory', required=True),
'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"),
}
document_directory_dctx()
class document_directory_node(osv.osv):
_inherit = 'process.node'
_columns = {
'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
}
document_directory_node()
+282
View File
@@ -0,0 +1,282 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
#
# Copyright (C) P. Christeas, 2009, all rights reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
import os
import tools
import base64
from tools.misc import ustr
from osv.orm import except_orm
import random
import string
import netsvc
from content_index import cntIndex
""" The algorithm of data storage
We have to consider 3 cases of data /retrieval/:
Given (context,path) we need to access the file (aka. node).
given (directory, context), we need one of its children (for listings, views)
given (ir.attachment, context), we needs its data and metadata (node).
For data /storage/ we have the cases:
Have (ir.attachment, context), we modify the file (save, update, rename etc).
Have (directory, context), we create a file.
Have (path, context), we create or modify a file.
Note that in all above cases, we don't explicitly choose the storage media,
but always require a context to be present.
Note that a node will not always have a corresponding ir.attachment. Dynamic
nodes, for once, won't. Their metadata will be computed by the parent storage
media + directory.
The algorithm says that in any of the above cases, our first goal is to locate
the node for any combination of search criteria. It would be wise NOT to
represent each node in the path (like node[/] + node[/dir1] + node[/dir1/dir2])
but directly jump to the end node (like node[/dir1/dir2]) whenever possible.
We also contain all the parenting loop code in one function. This is intentional,
because one day this will be optimized in the db (Pg 8.4).
"""
def random_name():
random.seed()
d = [random.choice(string.ascii_letters) for x in xrange(10) ]
name = "".join(d)
return name
INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
def create_directory(path):
dir_name = random_name()
path = os.path.join(path,dir_name)
os.makedirs(path)
return dir_name
class document_storage(osv.osv):
""" The primary object for data storage.
Each instance of this object is a storage media, in which our application
can store contents. The object here controls the behaviour of the storage
media.
The referring document.directory-ies will control the placement of data
into the storage.
It is a bad idea to have multiple document.storage objects pointing to
the same tree of filesystem storage.
"""
_name = 'document.storage'
_description = 'Document storage media'
_columns = {
'name': fields.char('Name', size=64, required=True, select=1),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'user_id': fields.many2one('res.users', 'Owner'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'dir_ids': fields.one2many('document.directory', 'parent_id', 'Directories'),
'type': fields.selection([('db','Database'),('filestore','Internal File storage'),
('realstore','External file storage'), ('virtual','Virtual storage')], 'Type', required=True),
'path': fields.char('Path',size=250,select=1,help="For file storage, the root path of the storage"),
'online': fields.boolean('Online',help="If not checked, media is currently offline and its contents not available", required=True),
'readonly': fields.boolean('Read Only', help="If set, media is for reading only"),
}
def _get_rootpath(self,cr,uid,context=None):
from tools import config
return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'online': lambda *args: True,
'readonly': lambda *args: False,
# Note: the defaults below should only be used ONCE for the default
# storage media. All other times, we should create different paths at least.
'type': lambda *args: 'filestore',
'path': _get_rootpath,
}
_sql_constraints = [
# SQL note: a path = NULL doesn't have to be unique.
('path_uniq', 'UNIQUE(type,path)', "The storage path must be unique!")
]
def get_data(self, cr,uid, id, file_node,context = None, fil_obj = None):
""" retrieve the contents of some file_node having storage_id = id
optionally, fil_obj could point to the browse object of the file
(ir.attachment)
"""
if not context:
context = {}
boo = self.browse(cr,uid,id,context)
if fil_obj:
ira = fil_obj
else:
ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
return self.__get_data_3(cr,uid,boo, ira, context)
def __get_data_3(self,cr,uid, boo, ira, context):
if not boo.online:
raise RuntimeError('media offline')
if boo.type == 'filestore':
if not ira.store_fname:
# On a migrated db, some files may have the wrong storage type
# try to fix their directory.
if ira.file_size:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"ir.attachment #%d does not have a filename, but is at filestore, fix it!" %ira.id)
return None
fpath = os.path.join(boo.path,ira.store_fname)
print "Trying to read \"%s\".."% fpath
return file(fpath,'rb').read()
elif boo.type == 'db':
# TODO: we need a better api for large files
if ira.db_datas:
out=base64.decodestring(ira.db_datas)
else:
out=''
return out
elif boo.type == 'realstore':
# fpath = os.path.join(boo.path,
return None
else:
raise TypeError("No %s storage" % boo.type)
def set_data(self, cr,uid, id, file_node, data, context = None, fil_obj = None):
""" store the data.
This function MUST be used from an ir.attachment. It wouldn't make sense
to store things persistently for other types (dynamic).
"""
if not context:
context = {}
boo = self.browse(cr,uid,id,context)
logger = netsvc.Logger()
if fil_obj:
ira = fil_obj
else:
ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
if not boo.online:
raise RuntimeError('media offline')
logger.notifyChannel('document',netsvc.LOG_DEBUG,"Store data for ir.attachment #%d" %ira.id)
store_fname = None
fname = None
if boo.type == 'filestore':
path = boo.path
try:
flag = None
# This can be improved
if os.path.isdir(path):
for dirs in os.listdir(path):
if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
flag = dirs
break
flag = flag or create_directory(path)
filename = random_name()
fname = os.path.join(path, flag, filename)
fp = file(fname,'wb')
fp.write(data)
fp.close()
logger.notifyChannel('document',netsvc.LOG_DEBUG,"Saved data to %s" % fname)
filesize = len(data) # os.stat(fname).st_size
store_fname = os.path.join(flag,filename)
# TODO Here, an old file would be left hanging.
except Exception,e :
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
raise except_orm(_('Error!'), str(e))
elif boo.type == 'db':
filesize = len(data)
# will that work for huge data? TODO
out=base64.encodestring(data)
cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
( out, file_node.file_id ))
else:
raise TypeError("No %s storage" % boo.type)
# 2nd phase: store the metadata
try:
icont = ''
mime = ira.file_type
try:
mime,icont = cntIndex.doIndex(data, ira.datas_fname,
ira.file_type or None,fname)
except Exception,e:
logger.notifyChannel('document', netsvc.LOG_DEBUG, 'Cannot index file: %s' % str(e))
pass
# a hack: /assume/ that the calling write operation will not try
# to write the fname and size, and update them in the db concurrently.
# We cannot use a write() here, because we are already in one.
cr.execute('UPDATE ir_attachment SET store_fname = %s, file_size = %s, index_content = %s, file_type = %s WHERE id = %s',
(store_fname, filesize, ustr(icont), mime, file_node.file_id ))
file_node.content_length = filesize
file_node.content_type = mime
return True
except Exception,e :
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
# should we really rollback once we have written the actual data?
# at the db case (only), that rollback would be safe
raise except_orm(_('Error at doc write!'), str(e))
def prepare_unlink(self,cr,uid,storage_bo, fil_bo):
""" Before we unlink a file (fil_boo), prepare the list of real
files that have to be removed, too. """
if not storage_bo.online:
raise RuntimeError('media offline')
if storage_bo.type == 'filestore':
fname = fil_bo.store_fname
if not fname:
return None
path = storage_bo.path
return ( storage_bo.id, 'file', os.path.join(path,fname))
elif storage_bo.type == 'db':
return None
else:
raise TypeError("No %s storage" % boo.type)
def do_unlink(self, cr,uid,unres):
for id, ktype, fname in unres:
if ktype == 'file':
try:
os.unlink(fname)
except Exception,e:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Could not remove file %s, please remove manually." % fname)
else:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Unknown unlink key %s" % ktype)
return True
document_storage()
#eof
+69 -98
View File
@@ -3,6 +3,48 @@
<menuitem name="Document Management" icon="terp-stock" id="menu_document"/>
<menuitem name="Document Configuration" id="menu_document_configuration" parent="menu_document"/>
<record model="ir.ui.view" id="view_document_storage_form">
<field name="name">document.storage</field>
<field name="model">document.storage</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Storage Media">
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="type" />
<field name="online" />
<field name="readonly" />
<field name="path" />
</form>
</field>
</record>
<record model="ir.ui.view" id="view_document_storage_tree">
<field name="name">document.storage</field>
<field name="model">document.storage</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Storage Media" toolbar="1">
<field name="name"/>
<field name="type"/>
<field name="online"/>
<field name="readonly"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_document_storage_form">
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.storage</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
name="Storage Media"
action="action_document_storage_form"
id="menu_document_storage_media"
parent="menu_document_configuration"/>
<record model="ir.ui.view" id="view_document_directory_form">
<field name="name">document.directory</field>
<field name="model">document.directory</field>
@@ -12,6 +54,7 @@
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="parent_id"/>
<field name="storage_id" />
<notebook colspan="4">
<page string="Definition">
<separator string="Directory Type" colspan="4"/>
@@ -20,14 +63,17 @@
<newline/>
<field name="domain" attrs="{'required': [('type','=','ressource')], 'readonly': [('type','=','static')]}"/>
<field name="ressource_tree"/>
<field name="resource_field"/>
<field name="ressource_id" select="2" readonly="1"/>
<field name="ressource_parent_type_id"/>
<separator string="Auto-Generated Files" colspan="4"/>
</page>
<page string="Generated Files">
<label colspan="4" string="For each entry here, virtual files will appear in this folder." />
<field name="content_ids" nolabel="1" colspan="4">
<form string="Contents">
<field name="name"/>
<field name="sequence"/>
<field name="prefix"/>
<field name="suffix"/>
<field name="extension"/>
<field name="include_name"/>
@@ -41,7 +87,20 @@
<field name="extension"/>
</tree>
</field>
</page><page string="Security">
</page>
<page string="Dynamic context">
<field name="dctx_ids" nolabel="1" colspan="4">
<tree string="Fields" editable="bottom">
<field name="field"/>
<field name="expr"/>
</tree>
<form string="Fields">
<field name="field"/>
<field name="expr"/>
</form>
</field>
</page>
<page string="Security">
<field name="group_ids" colspan="4" nolabel="1"/>
</page>
</notebook>
@@ -87,19 +146,7 @@
<menuitem
action="action_document_directory_tree"
id="menu_document_directories_tree"
parent="menu_document_configuration"/>
<record model="ir.actions.url" id="action_document_browse">
<field name="name">Browse Files</field>
<field name="url">ftp://localhost:8021/</field>
</record>
<menuitem
name="Browse Files Using FTP"
action="action_document_browse"
id="menu_document_browse"
type="url"
icon="STOCK_EXECUTE"
parent="menu_document"/>
parent="menu_document_configuration"/>
<record model="ir.ui.view" id="view_document_file_form">
<field name="name">ir.attachment</field>
@@ -151,7 +198,7 @@
<group colspan="2" col="2">
<separator string="File Information" colspan="2"/>
<field name="file_size" readonly="1"/>
<field name="parent_id" readonly="1" select="2"/>
<field name="parent_id" select="2"/>
</group>
</page><page string="Notes">
<field colspan="4" name="description" nolabel="1"/>
@@ -242,87 +289,11 @@
</field>
</field>
</record>
<!-- initial ftp server address, port configuration -->
<record id="view_auto_config_form" model="ir.ui.view">
<field name="name">Auto Configure Directory</field>
<field name="model">document.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Auto Configure">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will automatically configure the document management system according to modules installed on your system." align="0.0" colspan="4"/>
<separator string="" colspan="4"/>
<label string="Choose the ftp server address and port. These values will be stored in the configuration file." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<field name="suggested_host" colspan="4"/>
<field name="port" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
</form>
</field>
</record>
<record id="action_config_auto_directory" model="ir.actions.act_window">
<field name="name">Auto Configure Directory</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.actions.todo" id="config_auto_directory">
<field name="name">Auto Configure Directory</field>
<field name="note">This wizard will configure the URL of the server of the document management system. These ftp server address and port will be stored in the configuration file.</field>
<field name="action_id" ref="action_config_auto_directory"/>
</record>
<!-- ftp server address, port configuration -->
<record id="view_config_form" model="ir.ui.view">
<field name="name">Configure FTP server address</field>
<field name="model">document.configuration.ftp_server.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="FTP Server Configuration">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will configure the ftp server address and port." align="0.0" colspan="4"/>
<label string="These values will be stored in the configuration file." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<field name="suggested_host" colspan="4"/>
<field name="port" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
<group col="4" colspan="4">
<label string="Don't forget to restart the Open ERP server if you change these values !" align="0.0" colspan="4"/>
</group>
</form>
</field>
</record>
<record id="action_config_ftp_server" model="ir.actions.act_window">
<field name="name">Configure FTP server address</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.ftp_server.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="FTP Server Configuration"
id="menu_document_configuration_ftpserver"
action="action_config_ftp_server"
parent="menu_document_configuration"/>
<act_window domain="[('partner_id', '=', active_id)]"
id="act_res_partner_document" name="Related Documents"
res_model="ir.attachment"
src_model="res.partner"/>
</data>
</openerp>
+826
View File
@@ -0,0 +1,826 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import pooler
from tools.safe_eval import safe_eval
import os
import time
#
# An object that represent an uri
# path: the uri of the object
# content: the Content it belongs to (_print.pdf)
# type: content or collection
# content: objct = res.partner
# collection: object = directory, object2 = res.partner
# file: objct = ir.attachement
# root: if we are at the first directory of a ressource
#
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 """
cached_roots = {}
def __init__(self, cr, uid, context=None):
# we don't cache the cr!
self.dbname = cr.dbname
self.uid = uid
self.context = context
self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
assert self._dirobj
self.rootdir = self._dirobj._get_root_directory(cr,uid,context)
def get_uri(self, cr, uri):
""" Although this fn passes back to doc.dir, it is needed since
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])
if not ndir:
return False
duri = duri[1:]
return ndir
class node_database():
""" A node representing the database directory
Useless?
"""
def __init__(self,ncontext):
self.nctx = ncontext
class node_class(object):
""" this is a superclass for our inodes
It is an API for all code that wants to access the document files.
Nodes have attributes which contain usual file properties
"""
our_type = 'baseclass'
def __init__(self, path, parent, context):
assert isinstance(context,node_context)
assert (not parent ) or isinstance(parent,node_class)
self.path = path
self.context = context
self.type=self.our_type
self.parent = parent
self.mimetype = 'application/octet-stream'
self.create_date = None
self.write_date = None
self.content_length = 0
# dynamic context:
self.dctx = {}
if parent:
self.dctx = parent.dctx.copy()
self.displayname = 'Object'
def full_path(self):
""" Return the components of the full path for some
node.
The returned list only contains the names of nodes.
"""
if self.parent:
s = self.parent.full_path()
else:
s = []
if isinstance(self.path,list):
s+=self.path
else:
s.append(self.path)
return s #map(lambda x: '/' +x, s)
def children(self, cr):
print "node_class.children()"
return [] #stub
def child(self,cr, name):
print "node_class.child()"
return None
def path_get(self):
print "node_class.path_get()"
return False
def get_data(self,cr):
raise TypeError('no data for %s'% self.type)
def _get_storage(self,cr):
raise RuntimeError("no storage for base class")
def get_etag(self,cr):
""" 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)
def _get_wtag(self,cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
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 RuntimeError("get_etag stub()")
def get_dav_props(self, cr):
""" If this class has special behaviour for GroupDAV etc, export
its capabilities """
return {}
def get_dav_eprop(self,cr,ns,prop):
return None
class node_dir(node_class):
our_type = 'collection'
def __init__(self,path, parent, context, dirr, dctx=None):
super(node_dir,self).__init__(path, parent,context)
self.dir_id = dirr.id
#todo: more info from dirr
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
self.create_date = dirr.create_date
self.domain = dirr.domain
self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
# TODO: the write date should be MAX(file.write)..
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
if dctx:
self.dctx.update(dctx)
dc2 = self.context.context
dc2.update(self.dctx)
dc2['dir_id'] = self.dir_id
self.displayname = dirr.name
for dfld in dirr.dctx_ids:
try:
self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
except Exception,e:
print "Cannot eval %s" % dfld.expr
print e
pass
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
res = self._file_get(cr,name)
if res:
return res[0]
return None
def _file_get(self,cr, nodename=False):
res = []
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr, uid, where, context=ctx)
for content in cntobj.browse(cr, uid, ids, context=ctx):
res3 = cntobj._file_get(cr, self, nodename, content)
if res3:
res.extend(res3)
return res
def get_dav_props(self, cr):
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
res['http://groupdav.org/'] = ('resourcetype',)
break
return res
def get_dav_eprop(self,cr,ns,prop):
if ns != 'http://groupdav.org/' or prop != 'resourcetype':
print "Who asked for %s:%s?" % (ns,prop)
return None
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
return ('vevent-collection','http://groupdav.org/')
return None
def _child_get(self,cr,name = None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('parent_id','=',self.dir_id) ]
if name:
where.append(('name','=',name))
ids = dirobj.search(cr, uid, where + [('ressource_parent_type_id','=',False)],context=ctx)
res = []
if ids:
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
if dirr.type == 'directory':
res.append(node_dir(dirr.name,self,self.context,dirr))
elif dirr.type == 'ressource':
res.append(node_res_dir(dirr.name,self,self.context,dirr))
fil_obj=dirobj.pool.get('ir.attachment')
#where2 = where # + [('res_model', '=', None)]
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))
return res
def create_child(self,cr,path,data):
""" API function to create a child file object and node
Return the node_* created
"""
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
fil_obj=dirobj.pool.get('ir.attachment')
val = {
'name': path,
'datas_fname': path,
'parent_id': self.dir_id,
# Datas are not set here
}
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)
fnode.set_data(cr,data,fil)
return fnode
def _get_ttag(self,cr):
return 'dir-%d' % self.dir_id
class node_res_dir(node_class):
""" 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'
def __init__(self,path, parent, context, dirr, dctx=None ):
super(node_res_dir,self).__init__(path, parent,context)
self.dir_id = dirr.id
#todo: more info from dirr
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
self.create_date = dirr.create_date
# TODO: the write date should be MAX(file.write)..
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
self.resm_id = dirr.ressource_id
self.namefield = dirr.resource_field or 'name'
self.displayname = dirr.name
# Important: the domain is evaluated using the *parent* dctx!
self.domain = dirr.domain
self.ressource_tree = dirr.ressource_tree
# and then, we add our own vars in the dctx:
if dctx:
self.dctx.update(dctx)
# and then, we prepare a dctx dict, for deferred evaluation:
self.dctx_dict = {}
for dfld in dirr.dctx_ids:
self.dctx_dict['dctx_' + dfld.field] = dfld.expr
def children(self,cr):
return self._child_get(cr)
def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
return None
def _child_get(self,cr,name = None):
""" return virtual children of resource, based on the
foreign object.
Note that many objects use NULL for a name, so we should
better call the name_search(),name_get() set of methods
"""
obj = self.context._dirobj.pool.get(self.res_model)
if not obj:
print "couldn't find model", self.res_model
return []
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = []
if self.domain:
where += safe_eval(self.domain, self.dctx)
if self.resm_id:
where.append(('id','=',self.resm_id))
if name:
where.append((self.namefield,'=',name))
# print "Where clause for %s" % self.res_model, where
if self.ressource_tree:
object2 = False
if self.resm_id:
object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
if obj._parent_name in obj.fields_get(cr, uid):
where.append((obj._parent_name,'=',object2 and object2.id or False))
resids = obj.search(cr,uid, where, context=ctx)
res = []
for bo in obj.browse(cr,uid,resids,context=ctx):
if not bo:
continue
name = getattr(bo,self.namefield)
if not name:
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))
return res
def _get_ttag(self,cr):
return 'rdir-%d' % self.dir_id
class node_res_obj(node_class):
""" 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'
def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
super(node_res_obj,self).__init__(path, parent,context)
assert parent
#todo: more info from dirr
self.dir_id = dir_id
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
self.create_date = parent.create_date
# TODO: the write date should be MAX(file.write)..
self.write_date = parent.write_date
self.content_length = 0
self.res_model = res_model
self.domain = parent.domain
self.displayname = path
self.dctx_dict = parent.dctx_dict
if res_bo:
self.res_id = res_bo.id
dc2 = self.context.context
dc2.update(self.dctx)
dc2['res_model'] = res_model
dc2['res_id'] = res_bo.id
dc2['this'] = res_bo
for fld,expr in self.dctx_dict.items():
try:
self.dctx[fld] = safe_eval(expr,dc2)
except Exception,e:
print "Cannot eval %s for %s" % (expr, fld)
print e
pass
else:
self.res_id = res_id
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
res = self._file_get(cr,name)
if res:
return res[0]
return None
def _file_get(self,cr, nodename=False):
res = []
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
#if self.domain:
# where.extend(self.domain)
# print "res_obj file_get clause", where
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
res3 = cntobj._file_get(cr,self,nodename,content, context=ctx)
if res3:
res.extend(res3)
return res
def get_dav_props(self, cr):
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
res['http://groupdav.org/'] = ('resourcetype',)
return res
def get_dav_eprop(self,cr,ns,prop):
if ns != 'http://groupdav.org/' or prop != 'resourcetype':
print "Who asked for %s:%s?" % (ns,prop)
return None
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
return ('vevent-collection','http://groupdav.org/')
return None
def _child_get(self,cr,name = None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
directory = dirobj.browse(cr, uid, self.dir_id)
obj = dirobj.pool.get(self.res_model)
where = []
res = []
if name:
where.append(('name','=',name))
# Directory Structure display in tree structure
if self.res_id and directory.ressource_tree:
where1 = []
if obj._parent_name in obj.fields_get(cr, uid):
where1 = where + [(obj._parent_name, '=', self.res_id)]
resids = obj.search(cr,uid, where1, context=ctx)
for bo in obj.browse(cr,uid,resids,context=ctx):
namefield = directory.resource_field or 'name'
if not bo:
continue
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))
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))
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}))
fil_obj = dirobj.pool.get('ir.attachment')
where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
# print "where clause for dir_obj", where2
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))
# Get Child Ressource Directories
if directory.ressource_type_id and directory.ressource_type_id.id:
where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
where5 = where4 + [('ressource_id','=',0)]
dirids = dirobj.search(cr,uid, where5)
where5 = where4 + [('ressource_id','=',self.res_id)]
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))
if dirr.type == 'ressource':
res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
return res
def create_child(self,cr,path,data):
""" API function to create a child file object and node
Return the node_* created
"""
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
fil_obj=dirobj.pool.get('ir.attachment')
val = {
'name': path,
'datas_fname': path,
'parent_id': self.dir_id,
'res_model': self.res_model,
'res_id': self.res_id,
# Datas are not set here
}
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)
fnode.set_data(cr,data,fil)
return fnode
def _get_ttag(self,cr):
return 'rodir-%d-%d' % (self.dir_id,self.res_id)
class node_file(node_class):
our_type = 'file'
def __init__(self,path, parent, context, fil):
super(node_file,self).__init__(path, parent,context)
self.file_id = fil.id
#todo: more info from ir_attachment
if fil.file_type and '/' in fil.file_type:
self.mimetype = fil.file_type
self.create_date = fil.create_date
self.write_date = fil.write_date or fil.create_date
self.content_length = fil.file_size
self.displayname = fil.name
# This only propagates the problem to get_data. Better
# fix those files to point to the root dir.
if fil.parent_id:
self.storage_id = fil.parent_id.storage_id.id
else:
self.storage_id = None
def get_data(self, cr, fil_obj = None):
""" Retrieve the data for some file.
fil_obj may optionally be specified, and should be a browse object
for the file. This is useful when the caller has already initiated
the browse object. """
# this is where storage kicks in..
stor = self.storage_id
assert stor
stobj = self.context._dirobj.pool.get('document.storage')
return stobj.get_data(cr,self.context.uid,stor, self,self.context.context, fil_obj)
def get_data_len(self, cr, fil_obj = None):
# TODO: verify with the storage object!
return self.content_length
def set_data(self, cr, data, fil_obj = None):
""" Store data at some file.
fil_obj may optionally be specified, and should be a browse object
for the file. This is useful when the caller has already initiated
the browse object. """
# this is where storage kicks in..
stor = self.storage_id
assert stor
stobj = self.context._dirobj.pool.get('document.storage')
return stobj.set_data(cr,self.context.uid,stor, self, data, self.context.context, fil_obj)
def _get_ttag(self,cr):
return 'file-%d' % self.file_id
class node_content(node_class):
our_type = 'content'
def __init__(self,path, parent, context, cnt, dctx = None, act_id=None):
super(node_content,self).__init__(path, parent,context)
self.cnt_id = cnt.id
self.create_date = False
self.write_date = False
self.content_length = False
self.extension = cnt.extension
self.report_id = cnt.report_id and cnt.report_id.id
#self.mimetype = cnt.extension.
self.displayname = path
if dctx:
self.dctx.update(dctx)
self.act_id = act_id
def fill_fields(self,cr,dctx = None):
""" Try to read the object and fill missing fields, like mimetype,
dates etc.
This function must be different from the constructor, because
it uses the db cursor.
"""
cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
(self.extension,))
res = cr.fetchall()
if res and res[0][0]:
self.mimetype = res[0][0]
def get_data(self, cr, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
ctx = self.context.context.copy()
ctx.update(self.dctx)
data = cntobj.process_read(cr,self.context.uid,self,ctx)
if data:
self.content_length = len(data)
return data
def get_data_len(self, cr, fil_obj = None):
if not self.content_length:
self.get_data(cr,fil_obj)
return self.content_length
def set_data(self, cr, data, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
ctx = self.context.context.copy()
ctx.update(self.dctx)
return cntobj.process_write(cr,self.context.uid,self, data,ctx)
def _get_ttag(self,cr):
return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
class old_class():
# the old code, remove..
def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
self.cr = cr
def _file_get(self, nodename=False):
if not self.object:
return []
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
res2 = []
where = []
if self.object2:
where.append( ('res_model','=',self.object2._name) )
where.append( ('res_id','=',self.object2.id) )
else:
where.append( ('parent_id','=',self.object.id) )
where.append( ('res_id','=',False) )
if nodename:
where.append( (fobj._rec_name,'=',nodename) )
for content in self.object.content_ids:
res3 = content._table._file_get(self,nodename,content)
if res3:
res2.extend(res3)
ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
if self.object and self.root and (self.object.type=='ressource'):
ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
res = fobj.browse(self.cr, self.uid, ids, context=self.context)
return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
def get_translation(self,value,lang):
# Must go, it works on arbitrary models and could be ambiguous.
result = value
pool = pooler.get_pool(self.cr.dbname)
translation_ids = pool.get('ir.translation').search(self.cr, self.uid, [('value','=',value),('lang','=',lang),('type','=','model')])
if len(translation_ids):
tran_id = translation_ids[0]
translation = pool.get('ir.translation').read(self.cr, self.uid, tran_id, ['res_id','name'])
res_model,field_name = tuple(translation['name'].split(','))
res_id = translation['res_id']
res = pool.get(res_model).read(self.cr, self.uid, res_id, [field_name])
if res:
result = res[field_name]
return result
def directory_list_for_child(self,nodename,parent=False):
pool = pooler.get_pool(self.cr.dbname)
where = []
if nodename:
nodename = self.get_translation(nodename, self.context['lang'])
where.append(('name','=',nodename))
if (self.object and self.object.type=='directory') or not self.object2:
where.append(('parent_id','=',self.object and self.object.id or False))
else:
where.append(('parent_id','=',False))
if self.object:
where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
else:
where.append(('ressource_parent_type_id','=',False))
ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
if self.object2:
ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
return res
def _child_get(self, nodename=False):
if self.type not in ('collection','database'):
return []
res = self.directory_list_for_child(nodename)
result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
if self.type=='database':
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
vargs = [('parent_id','=',False),('res_id','=',False)]
if nodename:
vargs.append((fobj._rec_name,'=',nodename))
file_ids=fobj.search(self.cr,self.uid,vargs)
res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
if self.type=='collection' and self.object.type=="ressource":
where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
pool = pooler.get_pool(self.cr.dbname)
obj = pool.get(self.object.ressource_type_id.model)
_dirname_field = obj._rec_name
if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
_dirname_field = 'dirname'
name_for = obj._name.split('.')[-1]
if nodename and nodename.find(name_for) == 0 :
id = int(nodename.replace(name_for,''))
where.append(('id','=',id))
elif nodename:
if nodename.find('__') :
nodename=nodename.replace('__','/')
for invalid in INVALID_CHARS:
if nodename.find(INVALID_CHARS[invalid]) :
nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
nodename = self.get_translation(nodename, self.context['lang'])
where.append((_dirname_field,'=',nodename))
if self.object.ressource_tree:
if obj._parent_name in obj.fields_get(self.cr,self.uid):
where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
return result
else :
if self.object2:
return result
else:
if self.object2:
return result
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
for r in res:
if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
r.name = eval('r.'+_dirname_field)
else:
r.name = False
if not r.name:
r.name = name_for + '%d'%r.id
for invalid in INVALID_CHARS:
if r.name.find(invalid) :
r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
if result2:
if self.object.ressource_tree:
result += result2
else:
result = result2
return result
def path_get(self):
path = self.path
if self.path[0]=='/':
path = self.path[1:]
return path
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
@@ -5,7 +5,5 @@
"access_document_directory_content_all","document.directory.content all","model_document_directory_content",,1,0,0,0
"access_document_directory_content_group_document_manager","document.directory.content document manager","model_document_directory_content","group_document_manager",1,1,1,1
"access_document_directory_content_group_system","document.directory.content group system","model_document_directory_content","base.group_system",1,1,1,1
"access_document_configuation_wizard","document.configuration.wizard document manager","model_document_configuration_wizard","group_document_manager",1,1,1,1
"access_document_configuation_wizard_sytem","document.configuration.wizard group system","model_document_configuration_wizard","base.group_system",1,1,1,1
"access_document_directory_content_type_group_document_manager","document.directory.content.type document manager","model_document_directory_content_type","group_document_manager",1,1,1,1
"access_document_directory_content_type_group_system","document.directory.content.type group system","model_document_directory_content_type","base.group_user",1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_document_directory_content_all document.directory.content all model_document_directory_content 1 0 0 0
6 access_document_directory_content_group_document_manager document.directory.content document manager model_document_directory_content group_document_manager 1 1 1 1
7 access_document_directory_content_group_system document.directory.content group system model_document_directory_content base.group_system 1 1 1 1
access_document_configuation_wizard document.configuration.wizard document manager model_document_configuration_wizard group_document_manager 1 1 1 1
access_document_configuation_wizard_sytem document.configuration.wizard group system model_document_configuration_wizard base.group_system 1 1 1 1
8 access_document_directory_content_type_group_document_manager document.directory.content.type document manager model_document_directory_content_type group_document_manager 1 1 1 1
9 access_document_directory_content_type_group_system document.directory.content.type group system model_document_directory_content_type base.group_user 1 0 0 0
+111
View File
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import StringIO
import odt2txt
from content_index import indexer, cntIndex
def _to_unicode(s):
try:
return s.decode('utf-8')
except UnicodeError:
try:
return s.decode('latin')
except UnicodeError:
try:
return s.encode('ascii')
except UnicodeError:
return s
class TxtIndex(indexer):
def _getMimeTypes(self):
return ['text/plain','text/html','text/diff','text/xml', 'text/*',
'application/xml']
def _getExtensions(self):
return ['.txt', '.py']
def _doIndexContent(self,content):
return content
cntIndex.register(TxtIndex())
class DocIndex(indexer):
def _getMimeTypes(self):
return [ 'application/ms-word']
def _getExtensions(self):
return ['.doc']
def _doIndexFile(self,fname):
fp = Popen(['antiword',fname], shell=False, stdout=PIPE).stdout
return _to_unicode( fp.read())
cntIndex.register(DocIndex())
class PdfIndex(indexer):
def _getMimeTypes(self):
return [ 'application/pdf']
def _getExtensions(self):
return ['.pdf']
def _doIndexFile(self,fname):
fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE).stdout
return _to_unicode( fp.read())
cntIndex.register(PdfIndex())
class ImageNoIndex(indexer):
def _getMimeTypes(self):
return [ 'image/*']
def _getExtensions(self):
#better return no extension, and let 'file' do its magic
return []
#return ['.png','.jpg','.gif','.jpeg','.bmp','.tiff']
def _doIndexContent(self,content):
return 'image'
cntIndex.register(ImageNoIndex())
#class Doc(indexer):
#def _getDefMime(self,ext):
#def content_index(content, filename=None, content_type=None):
#fname,ext = os.path.splitext(filename)
#result = ''
#elif ext in ('.xls','.ods','.odt','.odp'):
#s = StringIO.StringIO(content)
#o = odt2txt.OpenDocumentTextFile(s)
#result = _to_unicode(o.toString())
#s.close()
#elif ext in ('.txt','.py','.patch','.html','.csv','.xml'):
#result = content
#else:
#result = content
#return result
#eof
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/python
import sys
import os
import glob
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
parser.add_option("-C", "--content",
action="store_true", dest="docontent", default=False,
help="Disect content, rather than the file.")
(options, args) = parser.parse_args()
import content_index, std_index
from content_index import cntIndex
for fname in args:
try:
if options.docontent:
fp = open(fname,'rb')
content = fp.read()
fp.close()
res = cntIndex.doIndex(content,fname,None,None,True)
else:
res = cntIndex.doIndex(None, fname, None, fname,True)
if res:
print "Result: ", res[0]
print res[1]
else:
print "No result"
except Exception,e:
import traceback,sys
tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
print "Traceback:",tb_s
print "Exception: ",e
#eof
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import doc_conf_wizard
import ftpserver
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+43
View File
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Integrated FTP Server with Document Management System',
'version': '1.99',
'category': 'Generic Modules/Others',
'description': """This is a support FTP Interface with document management system:
""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': ['base', 'document'],
'init_xml': [],
'update_xml': [
'document_ftp_view.xml',
'security/ir.model.access.csv'
],
'demo_xml': [],
'installable': True,
'active': False,
'certificate': None,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+163
View File
@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import os
class document_configuration_wizard(osv.osv_memory):
_name='document.configuration.wizard'
_rec_name = 'Auto Directory configuration'
_columns = {
'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
"Keep localhost if you don't know what to write.", required=True)
}
def detect_ip_addr(self, cr, uid, context=None):
def _detect_ip_addr(self, cr, uid, context=None):
from array import array
import socket
from struct import pack, unpack
try:
import fcntl
except ImportError:
fcntl = None
if not fcntl: # not UNIX:
host = socket.gethostname()
ip_addr = socket.gethostbyname(host)
else: # UNIX:
# get all interfaces:
nbytes = 128 * 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array('B', '\0' * nbytes)
outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
namestr = names.tostring()
ifaces = [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]
for ifname in [iface for iface in ifaces if iface != 'lo']:
ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
break
return ip_addr
try:
ip_addr = _detect_ip_addr(self, cr, uid, context)
except:
ip_addr = 'localhost'
return ip_addr
_defaults = {
'host': detect_ip_addr,
}
def action_cancel(self,cr,uid,ids,conect=None):
return {
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.actions.configuration.wizard',
'type': 'ir.actions.act_window',
'target':'new',
}
def action_config(self, cr, uid, ids, context=None):
conf = self.browse(cr, uid, ids[0], context)
obj=self.pool.get('document.directory')
objid=self.pool.get('ir.model.data')
if self.pool.get('sale.order'):
id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': '[]',
})
aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('document.directory.content').create(cr, uid, {
'name': "Print Order",
'suffix': "_print",
'report_id': aid,
'extension': '.pdf',
'include_name': 1,
'directory_id': id,
})
id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
id = objid.browse(cr, uid, id, context=context).res_id
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': "[('state','=','draft')]",
})
if self.pool.get('product.product'):
id = objid._get_id(cr, uid, 'document', 'dir_product')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
})
if self.pool.get('stock.location'):
aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('document.directory.content').create(cr, uid, {
'name': "Product Stock",
'suffix': "_stock_forecast",
'report_id': aid,
'extension': '.pdf',
'include_name': 1,
'directory_id': id,
})
if self.pool.get('account.analytic.account'):
id = objid._get_id(cr, uid, 'document', 'dir_project')
id = objid.browse(cr, uid, id, context=context).res_id
mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
obj.write(cr, uid, [id], {
'type':'ressource',
'ressource_type_id': mid[0],
'domain': '[]',
'ressource_tree': 1
})
# Update the action for FTP browse.
aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
aid = objid.browse(cr, uid, aid, context=context).res_id
self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':8021/'})
return {
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.actions.configuration.wizard',
'type': 'ir.actions.act_window',
'target': 'new',
}
document_configuration_wizard()
+56
View File
@@ -0,0 +1,56 @@
<openerp>
<data>
<record model="ir.actions.url" id="action_document_browse">
<field name="name">Browse Files</field>
<field name="url">ftp://localhost:8021/</field>
</record>
<menuitem
name="Browse Files Using FTP"
action="action_document_browse"
id="menu_document_browse"
type="url"
icon="STOCK_EXECUTE"
parent="document.menu_document"/>
<record id="view_auto_config_form" model="ir.ui.view">
<field name="name">Auto Configure Directory</field>
<field name="model">document.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Auto Configure">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will automatically configure the document management system according to modules installed on your system." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
</form>
</field>
</record>
<record id="action_config_auto_directory" model="ir.actions.act_window">
<field name="name">Auto Configure Directory</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.actions.todo" id="config_auto_directory">
<field name="name">Auto Configure Directory</field>
<field name="note">This wizard will configure the URL of the server of the document management system.</field>
<field name="action_id" ref="action_config_auto_directory"/>
</record>
</data>
</openerp>
@@ -28,7 +28,7 @@ import netsvc
from tools import config
from tools.misc import detect_ip_addr
HOST = ''
PORT = int(config.get('ftp_server_port', 8021))
PORT = 8021
PASSIVE_PORTS = None
pps = config.get('ftp_server_passive_ports', '').split(':')
if len(pps) == 2:
@@ -53,8 +53,7 @@ class ftp_server(threading.Thread):
ftpserver.logerror = lambda msg: self.log(netsvc.LOG_ERROR, msg)
HOST = config.get('ftp_server_address', detect_ip_addr())
PORT = int(config.get('ftp_server_port', '8021'))
PORT = int(config.get('ftp_server_port', '8021'))
address = (HOST, PORT)
ftpd = ftpserver.FTPServer(address, ftpserver.FTPHandler)
ftpd.serve_forever()
@@ -1,24 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# -*- encoding: utf-8 -*-
import os
import time
from tarfile import filemode
@@ -33,6 +13,7 @@ import netsvc
import os
from service import security
from osv import osv
from document.nodes import node_res_dir, node_res_obj
import stat
def log(message):
@@ -208,13 +189,13 @@ class abstracted_fs:
# Ok
def ftp2fs(self, path_orig, data):
path = self.ftpnorm(path_orig)
path = self.ftpnorm(path_orig)
if not data or (path and path=='/'):
return None
path2 = filter(None,path.split('/'))[1:]
(cr, uid, pool) = data
if len(path2):
path2[-1]=_to_unicode(path2[-1])
path2[-1]=_to_unicode(path2[-1])
res = pool.get('document.directory').get_object(cr, uid, path2[:])
if not res:
raise OSError(2, 'Not such file or directory.')
@@ -224,11 +205,13 @@ class abstracted_fs:
def fs2ftp(self, node):
res='/'
if node:
res=os.path.normpath(node.path)
paths = node.full_path()
paths = map(lambda x: '/' +x, paths)
res = os.path.normpath(''.join(paths))
res = res.replace("\\", "/")
while res[:2] == '//':
res = res[1:]
res='/' + node.cr.dbname + '/' + _to_decode(res)
res = '/' + node.context.dbname + '/' + _to_decode(res)
#res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/'
return res
@@ -250,46 +233,52 @@ class abstracted_fs:
# Ok
def create(self, node, objname, mode):
objname=_to_unicode(objname)
cr = node.cr
uid = node.uid
pool = pooler.get_pool(cr.dbname)
child = node.child(objname)
if child:
if child.type in ('collection','database'):
raise OSError(1, 'Operation not permited.')
if child.type=='content':
s = content_wrapper(cr.dbname, uid, pool, child)
return s
objname = _to_unicode(objname)
cr = None
try:
uid = node.context.uid
pool = pooler.get_pool(node.context.dbname)
cr = pooler.get_db(node.context.dbname).cursor()
child = node.child(cr, objname)
if child:
if child.type in ('collection','database'):
raise OSError(1, 'Operation not permited.')
if child.type == 'content':
s = content_wrapper(node.context.dbname, uid, pool, child)
return s
fobj = pool.get('ir.attachment')
ext = objname.find('.') >0 and objname.split('.')[1] or False
# TODO: test if already exist and modify in this case if node.type=file
### checked already exits
object2=node and node.object2 or False
object=node and node.object or False
cid=False
where=[('name','=',objname)]
object2 = False
if isinstance(node, node_res_obj):
object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
cid = False
object = node.context._dirobj.browse(cr, uid, node.dir_id)
where = [('name','=',objname)]
if object and (object.type in ('directory')) or object2:
where.append(('parent_id','=',object.id))
else:
where.append(('parent_id','=',False))
if object2:
where +=[('res_id','=',object2.id),('res_model','=',object2._name)]
where += [('res_id','=',object2.id),('res_model','=',object2._name)]
cids = fobj.search(cr, uid,where)
if len(cids):
cid=cids[0]
cid = cids[0]
if not cid:
val = {
'name': objname,
'datas_fname': objname,
'parent_id' : node.dir_id,
'datas': '',
'file_size': 0L,
'file_type': ext,
'store_method' : (object.storage_id.type == 'filestore' and 'fs')\
or (object.storage_id.type == 'db' and 'db')
}
if object and (object.type in ('directory')) or not object2:
val['parent_id']= object and object.id or False
@@ -307,28 +296,42 @@ class abstracted_fs:
cid = fobj.create(cr, uid, val, context={})
cr.commit()
s = file_wrapper('', cid, cr.dbname, uid, )
s = file_wrapper('', cid, node.context.dbname, uid, )
return s
except Exception,e:
except Exception,e:
log(e)
raise OSError(1, 'Operation not permited.')
finally:
if cr:
cr.close()
# Ok
def open(self, node, mode):
if not node:
raise OSError(1, 'Operation not permited.')
# Reading operation
if node.type=='file':
# Reading operation
if node.type == 'file':
cr = pooler.get_db(node.context.dbname).cursor()
uid = node.context.uid
if not self.isfile(node):
raise OSError(1, 'Operation not permited.')
s = StringIO.StringIO(base64.decodestring(node.object.datas or ''))
fobj = node.context._dirobj.pool.get('ir.attachment').browse(cr, uid, node.file_id, context=node.context.context)
if fobj.store_method and fobj.store_method== 'fs' :
s = StringIO.StringIO(node.get_data(cr, fobj))
else:
s = StringIO.StringIO(base64.decodestring(fobj.db_datas or ''))
s.name = node
cr.close()
return s
elif node.type=='content':
cr = node.cr
uid = node.uid
pool = pooler.get_pool(cr.dbname)
return getattr(pool.get('document.directory.content'), 'process_read_'+node.content.extension[1:])(cr, uid, node)
elif node.type == 'content':
uid = node.context.uid
cr = pooler.get_db(node.context.dbname).cursor()
pool = pooler.get_pool(node.context.dbname)
res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
res = StringIO.StringIO(res)
res.name = node
cr.close()
return res
else:
raise OSError(1, 'Operation not permited.')
@@ -359,7 +362,7 @@ class abstracted_fs:
if dir:
cr = dir.cr
uid = dir.uid
pool = pooler.get_pool(cr.dbname)
pool = pooler.get_pool(node.context.dbname)
object=dir and dir.object or False
object2=dir and dir.object2 or False
res=pool.get('ir.attachment').search(cr,uid,[('name','like',prefix),('parent_id','=',object and object.type in ('directory','ressource') and object.id or False),('res_id','=',object2 and object2.id or False),('res_model','=',object2 and object2._name or False)])
@@ -374,41 +377,49 @@ class abstracted_fs:
# Ok
def chdir(self, path):
if not path:
self.cwd='/'
return None
self.cwd = '/'
return None
if path.type in ('collection','database'):
self.cwd = self.fs2ftp(path)
elif path.type in ('file'):
parent_path = path.full_path()[:-1]
self.cwd = os.path.normpath(''.join(parent_path))
else:
raise OSError(1, 'Operation not permited.')
# Ok
def mkdir(self, node, basename):
"""Create the specified directory."""
cr = False
if not node:
raise OSError(1, 'Operation not permited.')
try:
basename=_to_unicode(basename)
object2=node and node.object2 or False
object=node and node.object or False
cr = node.cr
uid = node.uid
pool = pooler.get_pool(cr.dbname)
if node.object and (node.object.type=='ressource') and not node.object2:
basename =_to_unicode(basename)
cr = pooler.get_db(node.context.dbname).cursor()
uid = node.context.uid
pool = pooler.get_pool(node.context.dbname)
object2 = False
if isinstance(node, node_res_obj):
object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
obj = node.context._dirobj.browse(cr, uid, node.dir_id)
if obj and (obj.type == 'ressource') and not object2:
raise OSError(1, 'Operation not permited.')
val = {
'name': basename,
'ressource_parent_type_id': object and object.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False,
'parent_id' : False
}
if (object and (object.type in ('directory'))) or not object2:
val['parent_id'] = object and object.id or False
if (obj and (obj.type in ('directory'))) or not object2:
val['parent_id'] = obj and obj.id or False
# Check if it alreayd exists !
pool.get('document.directory').create(cr, uid, val)
cr.commit()
cr.commit()
except Exception,e:
log(e)
raise OSError(1, 'Operation not permited.')
finally:
if cr: cr.close()
# Ok
def close_cr(self, data):
@@ -436,8 +447,9 @@ class abstracted_fs:
# Ok
def listdir(self, path):
"""List the content of a directory."""
class false_node:
object = None
class false_node(object):
write_date = None
create_date = None
type = 'database'
def __init__(self, db):
self.path = '/'+db
@@ -449,108 +461,134 @@ class abstracted_fs:
uid = security.login(db, self.username, self.password)
if uid:
result.append(false_node(db))
except osv.except_osv:
except osv.except_osv:
pass
return result
return path.children()
cr = pooler.get_db(path.context.dbname).cursor()
res = path.children(cr)
cr.close()
return res
# Ok
def rmdir(self, node):
"""Remove the specified directory."""
cr = node.cr
uid = node.uid
pool = pooler.get_pool(cr.dbname)
object2=node and node.object2 or False
object=node and node.object or False
if object._table_name=='document.directory':
if node.children():
assert node
cr = pooler.get_db(node.context.dbname).cursor()
uid = node.context.uid
pool = pooler.get_pool(node.context.dbname)
object = node.context._dirobj.browse(cr, uid, node.dir_id)
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name == 'document.directory':
if node.children(cr):
raise OSError(39, 'Directory not empty.')
res = pool.get('document.directory').unlink(cr, uid, [object.id])
else:
raise OSError(39, 'Directory not empty.')
raise OSError(1, 'Operation not permited.')
cr.commit()
cr.close()
# Ok
def remove(self, node):
assert node
if node.type == 'collection':
return self.rmdir(node)
elif node.type == 'file':
return self.rmfile(node)
raise OSError(1, 'Operation not permited.')
def rmfile(self, node):
"""Remove the specified file."""
cr = node.cr
uid = node.uid
pool = pooler.get_pool(cr.dbname)
object2=node and node.object2 or False
object=node and node.object or False
assert node
if node.type == 'collection':
return self.rmdir(node)
uid = node.context.uid
pool = pooler.get_pool(node.context.dbname)
cr = pooler.get_db(node.context.dbname).cursor()
object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name=='ir.attachment':
if object._table_name == 'ir.attachment':
res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
else:
raise OSError(1, 'Operation not permited.')
cr.commit()
cr.close()
# Ok
def rename(self, src, dst_basedir,dst_basename):
def rename(self, src, dst_basedir, dst_basename):
"""
Renaming operation, the effect depends on the src:
* A file: read, create and remove
* A directory: change the parent and reassign childs to ressource
"""
cr = False
try:
dst_basename=_to_unicode(dst_basename)
if src.type=='collection':
if src.object._table_name <> 'document.directory':
dst_basename = _to_unicode(dst_basename)
cr = pooler.get_db(src.context.dbname).cursor()
uid = src.context.uid
if src.type == 'collection':
obj2 = False
dst_obj2 = False
pool = pooler.get_pool(src.context.dbname)
if isinstance(src, node_res_obj):
obj2 = src and pool.get(src.context.context['res_model']).browse(cr, uid, src.context.context['res_id']) or False
obj = src.context._dirobj.browse(cr, uid, src.dir_id)
if isinstance(dst_basedir, node_res_obj):
dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False
dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
if obj._table_name <> 'document.directory':
raise OSError(1, 'Operation not permited.')
result = {
'directory': [],
'attachment': []
}
# Compute all childs to set the new ressource ID
# Compute all childs to set the new ressource ID
child_ids = [src]
while len(child_ids):
node = child_ids.pop(0)
child_ids += node.children()
if node.type =='collection':
result['directory'].append(node.object.id)
if (not node.object.ressource_id) and node.object2:
node = child_ids.pop(0)
child_ids += node.children(cr)
if node.type == 'collection':
object2 = False
if isinstance(node, node_res_obj):
object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
object = node.context._dirobj.browse(cr, uid, node.dir_id)
result['directory'].append(object.id)
if (not object.ressource_id) and object2:
raise OSError(1, 'Operation not permited.')
elif node.type =='file':
result['attachment'].append(node.object.id)
cr = src.cr
uid = src.uid
pool = pooler.get_pool(cr.dbname)
object2=src and src.object2 or False
object=src and src.object or False
if object2 and not object.ressource_id:
elif node.type == 'file':
result['attachment'].append(object.id)
if obj2 and not obj.ressource_id:
raise OSError(1, 'Operation not permited.')
val = {
'name':dst_basename,
}
if (dst_basedir.object and (dst_basedir.object.type in ('directory'))) or not dst_basedir.object2:
val['parent_id'] = dst_basedir.object and dst_basedir.object.id or False
if (dst_obj and (dst_obj.type in ('directory'))) or not dst_obj2:
parent_id = dst_obj and dst_obj.id or False
else:
val['parent_id'] = False
res = pool.get('document.directory').write(cr, uid, [object.id],val)
if dst_basedir.object2:
ressource_type_id = pool.get('ir.model').search(cr,uid,[('model','=',dst_basedir.object2._name)])[0]
ressource_id = dst_basedir.object2.id
title = dst_basedir.object2.name
ressource_model = dst_basedir.object2._name
if dst_basedir.object2._name=='res.partner':
partner_id=dst_basedir.object2.id
else:
obj2=pool.get(dst_basedir.object2._name)
partner_id= obj2.fields_get(cr,uid,['partner_id']) and dst_basedir.object2.partner_id.id or False
parent_id = False
if dst_obj2:
ressource_type_id = pool.get('ir.model').search(cr, uid, [('model','=',dst_obj2._name)])[0]
ressource_id = dst_obj2.id
title = dst_obj2.name
ressource_model = dst_obj2._name
if dst_obj2._name == 'res.partner':
partner_id = dst_obj2.id
else:
partner_id = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
else:
ressource_type_id = False
ressource_id=False
ressource_id = False
ressource_model = False
partner_id = False
title = False
title = False
pool.get('document.directory').write(cr, uid, result['directory'], {
'name' : dst_basename,
'ressource_id': ressource_id,
'ressource_type_id': ressource_type_id
'ressource_parent_type_id': ressource_type_id,
'parent_id' : parent_id
})
val = {
'res_id': ressource_id,
@@ -560,11 +598,18 @@ class abstracted_fs:
}
pool.get('ir.attachment').write(cr, uid, result['attachment'], val)
if (not val['res_id']) and result['attachment']:
dst_basedir.cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')')
cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')')
cr.commit()
elif src.type=='file':
pool = pooler.get_pool(src.cr.dbname)
elif src.type == 'file':
pool = pooler.get_pool(src.context.dbname)
obj = pool.get('ir.attachment').browse(cr, uid, src.file_id)
dst_obj2 = False
if isinstance(dst_basedir, node_res_obj):
dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False
dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id)
val = {
'partner_id':False,
#'res_id': False,
@@ -574,40 +619,40 @@ class abstracted_fs:
'title': dst_basename,
}
if (dst_basedir.object and (dst_basedir.object.type in ('directory','ressource'))) or not dst_basedir.object2:
val['parent_id'] = dst_basedir.object and dst_basedir.object.id or False
if (dst_obj and (dst_obj.type in ('directory','ressource'))) or not dst_obj2:
val['parent_id'] = dst_obj and dst_obj.id or False
else:
val['parent_id'] = False
if dst_basedir.object2:
val['res_model'] = dst_basedir.object2._name
val['res_id'] = dst_basedir.object2.id
val['title'] = dst_basedir.object2.name
if dst_basedir.object2._name=='res.partner':
val['partner_id']=dst_basedir.object2.id
else:
obj2=pool.get(dst_basedir.object2._name)
val['partner_id']= obj2.fields_get(src.cr, src.uid,['partner_id']) and dst_basedir.object2.partner_id.id or False
elif src.object.res_id:
if dst_obj2:
val['res_model'] = dst_obj2._name
val['res_id'] = dst_obj2.id
val['title'] = dst_obj2.name
if dst_obj2._name == 'res.partner':
val['partner_id'] = dst_obj2.id
else:
val['partner_id'] = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False
elif obj.res_id:
# I had to do that because writing False to an integer writes 0 instead of NULL
# change if one day we decide to improve osv/fields.py
dst_basedir.cr.execute('update ir_attachment set res_id=NULL where id=%s', (src.object.id,))
cr.execute('update ir_attachment set res_id=NULL where id=%s', (obj.id,))
pool.get('ir.attachment').write(src.cr, src.uid, [src.object.id], val)
src.cr.commit()
pool.get('ir.attachment').write(cr, uid, [obj.id], val)
cr.commit()
elif src.type=='content':
src_file=self.open(src,'r')
dst_file=self.create(dst_basedir,dst_basename,'w')
src_file = self.open(src,'r')
dst_file = self.create(dst_basedir, dst_basename, 'w')
dst_file.write(src_file.getvalue())
dst_file.close()
src_file.close()
src.cr.commit()
cr.commit()
else:
raise OSError(1, 'Operation not permited.')
except Exception,err:
log(err)
raise OSError(1,'Operation not permited.')
finally:
if cr: cr.close()
@@ -650,15 +695,16 @@ class abstracted_fs:
"""Return the size of the specified file in bytes."""
result = 0L
if node.type=='file':
result = node.object.file_size or 0L
result = node.content_length or 0L
return result
# Ok
def getmtime(self, node):
"""Return the last modified time as a number of seconds since
the epoch."""
if node.object and node.type<>'content':
dt = (node.object.write_date or node.object.create_date)[:19]
if node.write_date or node.create_date:
dt = (node.write_date or node.create_date)[:19]
result = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S'))
else:
result = time.mktime(time.localtime())
@@ -1,26 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import pooler
from service import security
# -*- encoding: utf-8 -*-
class authorizer:
read_perms = "elr"
@@ -69,7 +47,7 @@ class authorizer:
paths = path.split('/')
if not len(paths)>2:
return True
db_name = paths[1]
db_name = paths[1]
db,pool = pooler.get_db_and_pool(db_name)
res = security.login(db_name, username, self.password)
return bool(res)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_document_configuation_wizard","document.configuration.wizard document manager","model_document_configuration_wizard","document.group_document_manager",1,1,1,1
"access_document_configuation_wizard_sytem","document.configuration.wizard group system","model_document_configuration_wizard","base.group_system",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_document_configuation_wizard document.configuration.wizard document manager model_document_configuration_wizard document.group_document_manager 1 1 1 1
3 access_document_configuation_wizard_sytem document.configuration.wizard group system model_document_configuration_wizard base.group_system 1 1 1 1
+1 -1
View File
@@ -19,6 +19,6 @@
#
##############################################################################
import document
import document_ics
import document_ics_config_wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-191
View File
@@ -1,191 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from osv.orm import except_orm
import os
import StringIO
import base64
import datetime
import time
import random
import tools
ICS_TAGS = {
'summary':'normal',
'uid':'normal' ,
'dtstart':'date' ,
'dtend':'date' ,
'created':'date' ,
'dtstamp':'date' ,
'last-modified':'normal' ,
'url':'normal' ,
'attendee':'multiple',
'location':'normal',
'categories': 'normal',
'description':'normal',
# TODO: handle the 'duration' property
}
class document_directory_ics_fields(osv.osv):
_name = 'document.directory.ics.fields'
_columns = {
'field_id': fields.many2one('ir.model.fields', 'Open ERP Field', required=True),
'name': fields.selection(map(lambda x: (x, x), ICS_TAGS.keys()), 'ICS Value', required=True),
'content_id': fields.many2one('document.directory.content', 'Content', required=True, ondelete='cascade')
}
document_directory_ics_fields()
class document_directory_content(osv.osv):
_inherit = 'document.directory.content'
_columns = {
'ics_object_id': fields.many2one('ir.model', 'Object'),
'ics_domain': fields.char('Domain', size=64),
'ics_field_ids': fields.one2many('document.directory.ics.fields', 'content_id', 'Fields Mapping')
}
_defaults = {
'ics_domain': lambda *args: '[]'
}
def process_write_ics(self, cr, uid, node, data, context={}):
import vobject
parsedCal = vobject.readOne(data)
fields = {}
fobj = self.pool.get('document.directory.content')
content = fobj.browse(cr, uid, node.content.id, context)
idomain = {}
for d in eval(content.ics_domain):
idomain[d[0]]=d[2]
for n in content.ics_field_ids:
fields[n.name] = n.field_id.name
if 'uid' not in fields:
return True
for child in parsedCal.getChildren():
result = {}
uuid = None
for event in child.getChildren():
if event.name.lower()=='uid':
uuid = event.value
if event.name.lower() in fields:
if ICS_TAGS[event.name.lower()]=='normal':
result[fields[event.name.lower()]] = event.value.encode('utf8')
elif ICS_TAGS[event.name.lower()]=='date':
result[fields[event.name.lower()]] = event.value.strftime('%Y-%m-%d %H:%M:%S')
if not uuid:
continue
fobj = self.pool.get(content.ics_object_id.model)
id = fobj.search(cr, uid, [(fields['uid'], '=', uuid.encode('utf8'))], context=context)
if id:
fobj.write(cr, uid, id, result, context=context)
else:
r = idomain.copy()
r.update(result)
fobj.create(cr, uid, r, context=context)
return True
def process_read_ics(self, cr, uid, node, context={}):
def ics_datetime(idate, short=False):
if short:
return datetime.date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
else:
return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
import vobject
obj_class = self.pool.get(node.content.ics_object_id.model)
# Can be improved to use context and active_id !
domain = eval(node.content.ics_domain)
ids = obj_class.search(cr, uid, domain, context)
cal = vobject.iCalendar()
for obj in obj_class.browse(cr, uid, ids, context):
event = cal.add('vevent')
# Fix dtstamp et last-modified with create and write date on the object line
perm = obj_class.perm_read(cr, uid, [obj.id], context)
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
event.add('dtstamp').value = ics_datetime(perm[0]['create_date'][:19])
if perm[0]['write_date']:
event.add('last-modified').value = ics_datetime(perm[0]['write_date'][:19])
for field in node.content.ics_field_ids:
value = getattr(obj, field.field_id.name)
value = value and tools.ustr(value)
if (not value) and field.name=='uid':
value = 'OpenERP-%s_%s@%s' % (node.content.ics_object_id.model, str(obj.id), cr.dbname,)
obj_class.write(cr, uid, [obj.id], {field.field_id.name: value})
if ICS_TAGS[field.name]=='normal':
if type(value)==type(obj):
value=value.name
value = value or ''
event.add(field.name).value = value or ''
elif ICS_TAGS[field.name]=='date' and value:
if field.name == 'dtstart':
date_start = start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value , "%Y-%m-%d %H:%M:%S")))
if field.name == 'dtend' and isinstance(value, float):
value = (start_date + datetime.timedelta(hours=value)).strftime('%Y-%m-%d %H:%M:%S')
if len(value)==10:
value = ics_datetime(value, True)
else:
value = ics_datetime(value)
event.add(field.name).value = value
s= StringIO.StringIO(cal.serialize())
s.name = node
cr.commit()
return s
document_directory_content()
class crm_case(osv.osv):
_inherit = 'crm.case'
_columns = {
'code': fields.char('Calendar Code', size=64),
'date_deadline': fields.datetime('Deadline', help="Deadline Date is automatically computed from Start Date + Duration"),
}
_defaults = {
'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.case'),
}
def copy(self, cr, uid, id, default=None, context=None):
"""
code field must be unique in ICS file
"""
if not default: default = {}
if not context: context = {}
default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.case'), 'id': False})
return super(crm_case, self).copy(cr, uid, id, default, context)
def on_change_duration(self, cr, uid, id, date, duration):
if not date:
return {}
start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(date, "%Y-%m-%d %H:%M:%S")))
if duration >= 0 :
end = start_date + datetime.timedelta(hours=duration)
if duration < 0:
raise osv.except_osv(_('Warning !'),
_('You can not set negative Duration.'))
res = {'value' : {'date_deadline' : end.strftime('%Y-%m-%d %H:%M:%S')}}
return res
crm_case()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+1
View File
@@ -5,6 +5,7 @@
<record model="document.directory.content.type" id="ics">
<field name="code">.ics</field>
<field name="name">ICS Calendar</field>
<field name="mimetype">text/calendar</field>
</record>
</data>
+337
View File
@@ -0,0 +1,337 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from osv.orm import except_orm
import os
import StringIO
import base64
import datetime
import time
import random
import tools
import re
from document.nodes import node_content
from tools.safe_eval import safe_eval
ICS_TAGS = {
'summary':'normal',
'uid':'normal' ,
'dtstart':'date' ,
'dtend':'date' ,
'created':'date' ,
'dtstamp':'date' ,
'last-modified':'normal' ,
'url':'normal' ,
'attendee':'multiple',
'location':'normal',
'categories': 'normal',
'description':'normal',
# TODO: handle the 'duration' property
}
ICS_FUNCTIONS = [
('field', 'Use the field'),
('const', 'Expression as constant'),
('hours', 'Interval in hours'),
]
class document_directory_ics_fields(osv.osv):
_name = 'document.directory.ics.fields'
_columns = {
'field_id': fields.many2one('ir.model.fields', 'Open ERP Field'),
'name': fields.selection(map(lambda x: (x, x), ICS_TAGS.keys()), 'ICS Value', required=True),
'content_id': fields.many2one('document.directory.content', 'Content', required=True, ondelete='cascade'),
'expr': fields.char("Expression", size=64),
'fn': fields.selection(ICS_FUNCTIONS,'Function',help="Alternate method of calculating the value", required=True)
}
_defaults = {
'fn': lambda *a: 'field',
}
document_directory_ics_fields()
class document_directory_content(osv.osv):
_inherit = 'document.directory.content'
__rege = re.compile(r'OpenERP-([\w|\.]+)_([0-9]+)@(\w+)$')
_columns = {
'object_id': fields.many2one('ir.model', 'Object', oldname= 'ics_object_id'),
'obj_iterate': fields.boolean('Iterate object',help="If set, a separate instance will be created for each record of Object"),
'fname_field': fields.char("Filename field",size=16,help="The field of the object used in the filename. Has to be a unique identifier."),
'ics_domain': fields.char('Domain', size=64),
'ics_field_ids': fields.one2many('document.directory.ics.fields', 'content_id', 'Fields Mapping')
}
_defaults = {
'ics_domain': lambda *args: '[]'
}
def _file_get(self, cr, node, nodename, content, context=None):
if not content.obj_iterate:
return super(document_directory_content, self)._file_get(cr, node, nodename, content)
else:
if not content.object_id:
return False
mod = self.pool.get(content.object_id.model)
uid = node.context.uid
fname_fld = content.fname_field or 'id'
where = []
if node.domain:
where += eval(node.domain)
if nodename:
# Reverse-parse the nodename to deduce the clause:
prefix = (content.prefix or '')
suffix = (content.suffix or '') + (content.extension or '')
if not nodename.startswith(prefix):
return False
if not nodename.endswith(suffix):
return False
tval = nodename[len(prefix):0 - len(suffix)]
where.append((fname_fld,'=',tval))
# print "ics iterate clause:", where
resids = mod.search(cr, uid, where, context=context)
if not resids:
return False
res2 = []
for ro in mod.read(cr,uid,resids,['id', fname_fld]):
tname = (content.prefix or '') + str(ro[fname_fld])
tname += (content.suffix or '') + (content.extension or '')
dctx2 = { 'active_id': ro['id'] }
if fname_fld:
dctx2['active_'+fname_fld] = ro[fname_fld]
n = node_content(tname, node, node.context,content,dctx=dctx2, act_id = ro['id'])
n.fill_fields(cr, dctx2)
res2.append(n)
return res2
def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.ics':
return super(document_directory_content, self).process_write(cr, uid, node, data, context)
import vobject
parsedCal = vobject.readOne(data)
fields = {}
funcs = {}
fexprs = {}
content = self.browse(cr, uid, node.cnt_id, context)
idomain = {}
ctx = (context or {})
ctx.update(node.context.context.copy())
ctx.update(node.dctx)
# print "ICS domain: ", type(content.ics_domain), content.ics_domain
if content.ics_domain:
for d in safe_eval(content.ics_domain,ctx):
# TODO: operator?
idomain[d[0]]=d[2]
for n in content.ics_field_ids:
fields[n.name] = n.field_id.name and str(n.field_id.name)
funcs[n.name] = n.fn
fexprs[n.name] = n.expr
if 'uid' not in fields:
print "uid not in ", fields
# FIXME: should pass
return True
for child in parsedCal.getChildren():
result = {}
uuid = None
for event in child.getChildren():
enl = event.name.lower()
if enl =='uid':
uuid = event.value
if not enl in fields:
# print "skip", enl
continue
if fields[enl] and funcs[enl] == 'field':
if ICS_TAGS[enl]=='normal':
result[fields[enl]] = event.value.encode('utf8')
elif ICS_TAGS[enl]=='date':
result[fields[enl]] = event.value.strftime('%Y-%m-%d %H:%M:%S')
# print "Field ",enl, result[fields[enl]]
elif fields[enl] and funcs[enl] == 'hours':
ntag = fexprs[enl] or 'dtstart'
ts_start = child.getChildValue(ntag, default=False)
if not ts_start:
raise Exception("Cannot parse hours (for %s) without %s" % (enl, ntag))
ts_end = event.value
assert isinstance(ts_start, datetime.datetime)
assert isinstance(ts_end, datetime.datetime)
td = ts_end - ts_start
result[fields[enl]] = td.days * 24.0 + ( td.seconds / 3600.0)
# put other functions here..
else:
# print "Unhandled tag in ICS:", enl
pass
# end for
if not uuid:
print "Skipping cal", child
# FIXME: should pass
continue
cmodel = content.object_id.model
wexpr = False
if fields['uid']:
wexpr = [(fields['uid'], '=', uuid.encode('utf8'))]
else:
# Parse back the uid from 'OpenERP-%s_%s@%s'
wematch = self.__rege.match(uuid.encode('utf8'))
# TODO: perhaps also add the domain to wexpr, restrict.
if not wematch:
raise Exception("Cannot locate UID in %s" % uuid)
if wematch.group(3) != cr.dbname:
raise Exception("Object is not for our db!")
if content.object_id:
if wematch.group(1) != cmodel:
raise Exception("ICS must be at the wrong folder, this one is for %s" % cmodel)
else:
# TODO: perhaps guess the model from the iCal, is it safe?
pass
wexpr = [ ( 'id', '=', wematch.group(2) ) ]
# print "Looking at ", cmodel, " for ", wexpr
# print "domain=", idomain
fobj = self.pool.get(content.object_id.model)
if not wexpr:
id = False
else:
id = fobj.search(cr, uid, wexpr, context=context)
if isinstance(id, list):
if len(id) > 1:
raise Exception("Multiple matches found for ICS")
if id:
print "writting at %s#%d:" %(cmodel, id[0]), result
fobj.write(cr, uid, id, result, context=context)
else:
r = idomain.copy()
r.update(result)
print "creating at %s#%d:" %(cmodel, id), result
fobj.create(cr, uid, r, context=context)
return True
def process_read(self, cr, uid, node, context=None):
def ics_datetime(idate, short=False):
if short:
return datetime.date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
else:
return datetime.datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
if node.extension != '.ics':
return super(document_directory_content, self).process_read(cr, uid, node, context)
import vobject
ctx = (context or {})
ctx.update(node.context.context.copy())
ctx.update(node.dctx)
content = self.browse(cr, uid, node.cnt_id, ctx)
if not content.object_id:
return super(document_directory_content, self).process_read(cr, uid, node, context)
obj_class = self.pool.get(content.object_id.model)
if content.ics_domain:
domain = safe_eval(content.ics_domain,ctx)
else:
domain = []
if node.act_id:
domain.append(('id','=',node.act_id))
# print "process read clause:",domain
ids = obj_class.search(cr, uid, domain, context=ctx)
cal = vobject.iCalendar()
for obj in obj_class.browse(cr, uid, ids):
event = cal.add('vevent')
# Fix dtstamp et last-modified with create and write date on the object line
perm = obj_class.perm_read(cr, uid, [obj.id], context)
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
event.add('dtstamp').value = ics_datetime(perm[0]['create_date'][:19])
if perm[0]['write_date']:
event.add('last-modified').value = ics_datetime(perm[0]['write_date'][:19])
for field in content.ics_field_ids:
if field.field_id.name:
value = getattr(obj, field.field_id.name)
else: value = None
if (not value) and field.name=='uid':
value = 'OpenERP-%s_%s@%s' % (content.object_id.model, str(obj.id), cr.dbname,)
# Why? obj_class.write(cr, uid, [obj.id], {field.field_id.name: value})
if ICS_TAGS[field.name]=='normal':
if type(value)==type(obj):
value=value.name
event.add(field.name).value = tools.ustr(value) or ''
elif ICS_TAGS[field.name]=='date' and value:
if field.name == 'dtstart':
date_start = start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value , "%Y-%m-%d %H:%M:%S")))
if field.name == 'dtend' and ( isinstance(value, float) or field.fn == 'hours'):
value = (start_date + datetime.timedelta(hours=value)).strftime('%Y-%m-%d %H:%M:%S')
if len(value)==10:
value = ics_datetime(value, True)
else:
value = ics_datetime(value)
event.add(field.name).value = value
s = cal.serialize()
return s
document_directory_content()
class crm_case(osv.osv):
_inherit = 'crm.case'
_columns = {
'code': fields.char('Calendar Code', size=64),
'date_deadline': fields.datetime('Deadline', help="Deadline Date is automatically computed from Start Date + Duration"),
}
_defaults = {
'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.case'),
}
def copy(self, cr, uid, id, default=None, context=None):
"""
code field must be unique in ICS file
"""
if not default: default = {}
if not context: context = {}
default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.case'), 'id': False})
return super(crm_case, self).copy(cr, uid, id, default, context)
def on_change_duration(self, cr, uid, id, date, duration):
if not date:
return {}
start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(date, "%Y-%m-%d %H:%M:%S")))
if duration >= 0 :
end = start_date + datetime.timedelta(hours=duration)
if duration < 0:
raise osv.except_osv(_('Warning !'),
_('You can not set negative Duration.'))
res = {'value' : {'date_deadline' : end.strftime('%Y-%m-%d %H:%M:%S')}}
return res
crm_case()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+18 -3
View File
@@ -1,20 +1,35 @@
<openerp>
<data>
<record model="ir.ui.view" id="view_document_directory_form">
<record model="ir.ui.view" id="view_document_directory_form_1">
<field name="name">document.directory</field>
<field name="model">document.directory</field>
<field name="type">form</field>
<field name="inherit_id" ref="document.view_document_directory_form"/>
<field name="arch" type="xml">
<field name="include_name" position="after">
<field name="object_id"/>
<field name="obj_iterate" />
<field name="fname_field" />
</field>
</field>
</record>
<record model="ir.ui.view" id="view_document_directory_form">
<field name="name">document.directory</field>
<field name="model">document.directory</field>
<field name="type">form</field>
<field name="inherit_id" ref="view_document_directory_form_1"/>
<field name="arch" type="xml">
<field name="report_id" position="after">
<separator string="ICS Calendar" colspan="4"/>
<field name="ics_object_id"/>
<field name="ics_domain"/>
<field name="ics_field_ids" colspan="4">
<tree string="ICS Mapping" editable="bottom">
<field name="name"/>
<field name="field_id" domain="[('model_id','=',parent.ics_object_id)]"/>
<field name="fn" />
<field name="field_id" domain="[('model_id','=?',parent.object_id)]"/>
<field name="expr" />
</tree>
</field>
</field>
+24 -24
View File
@@ -81,8 +81,8 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers=None):
""" send a body in one part """
if not headers:
headers = {}
if not headers:
headers = {}
self.send_response(code,message=msg)
self.send_header("Connection", "keep-alive")
self.send_header("Accept-Ranges", "bytes")
@@ -122,7 +122,7 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
self.send_header("Connection", "keep-alive")
self.send_header("Connection", "keep-alive")
self.send_header("DAV", "1")
self.end_headers()
@@ -162,9 +162,9 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
try:
DATA=pf.createResponse()
DATA=DATA+"\n"
# print "Data:", DATA
except DAV_NotFound,(ec,dd):
return self.send_notFound(dd, uri)
# print "Data:", DATA
except DAV_NotFound,(ec,dd):
return self.send_notFound(dd, uri)
except DAV_Error, (ec,dd):
return self.send_error(ec,dd)
@@ -172,10 +172,10 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def geturi(self):
buri = self.IFACE_CLASS.baseuri
if buri[-1] == '/':
return urllib.unquote(buri[:-1]+self.path)
else:
return urllib.unquote(buri+self.path)
if buri[-1] == '/':
return urllib.unquote(buri[:-1]+self.path)
else:
return urllib.unquote(buri+self.path)
def do_GET(self):
"""Serve a GET request."""
@@ -209,7 +209,7 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
""" Send a HEAD response """
dc=self.IFACE_CLASS
uri=self.geturi()
# get the last modified date
try:
lm=dc.get_prop(uri,"DAV:","getlastmodified")
@@ -243,12 +243,12 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
uri=self.geturi()
try:
res = dc.mkcol(uri)
if res:
self.send_body(None,201,"Created",'')
else:
self.send_body(None,415,"Cannot create",'')
#self.send_header("Connection", "keep-alive")
# Todo: some content, too
if res:
self.send_body(None,201,"Created",'')
else:
self.send_body(None,415,"Cannot create",'')
#self.send_header("Connection", "keep-alive")
# Todo: some content, too
except DAV_Error, (ec,dd):
self.send_body(None,int(ec),dd,dd)
@@ -370,10 +370,10 @@ class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_notFound(self,descr,uri):
body = """<?xml version="1.0" encoding="utf-8" ?>
<D:response xmlns:D="DAV:">
<D:href>%s</D:href>
<D:error/>
<D:responsedescription>%s</D:responsedescription>
</D:response>
"""
return self.send_status(404,descr, body=body % (uri,descr))
<D:response xmlns:D="DAV:">
<D:href>%s</D:href>
<D:error/>
<D:responsedescription>%s</D:responsedescription>
</D:response>
"""
return self.send_status(404,descr, body=body % (uri,descr))
+9 -9
View File
@@ -67,19 +67,19 @@ class dav_interface:
if self.M_NS.has_key(ns):
prefix=self.M_NS[ns]
else:
print "No namespace:",ns
print "No namespace:",ns, "( for prop:", propname,")"
raise DAV_NotFound
mname=prefix+"_"+propname
if not hasattr(self,mname):
raise DAV_NotFound
if not hasattr(self,mname):
raise DAV_NotFound
try:
m=getattr(self,mname)
r=m(uri)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
print 'Property %s not supported' % propname
print "Exception:", e
raise DAV_NotFound
###
@@ -110,15 +110,15 @@ class dav_interface:
""" return the creationdate of a resource """
d=self.get_creationdate(uri)
# format it
if isinstance(d, int) or isinstance(d, float):
d = time.localtimetime(d)
if isinstance(d, int) or isinstance(d, float):
d = time.localtimetime(d)
return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
def _get_dav_getlastmodified(self,uri):
""" return the last modified date of a resource """
d=self.get_lastmodified(uri)
if isinstance(d, int) or isinstance(d, float):
d = time.localtime(d)
if isinstance(d, int) or isinstance(d, float):
d = time.localtime(d)
# format it
return time.asctime(d)
+1 -1
View File
@@ -135,7 +135,7 @@ def get_parenturi(uri):
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
doc = Document.Document(None)
doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
+34 -34
View File
@@ -2,38 +2,38 @@ import time
import heapq
def memoize(maxsize):
"""decorator to 'memoize' a function - caching its results"""
def decorating_function(f):
cache = {} # map from key to value
heap = [] # list of keys, in LRU heap
cursize = 0 # because len() is slow
def wrapper(*args):
key = repr(args)
# performance crap
_cache=cache
_heap=heap
_heappop = heapq.heappop
_heappush = heapq.heappush
_time = time.time
_cursize = cursize
_maxsize = maxsize
if not _cache.has_key(key):
if _cursize == _maxsize:
# pop oldest element
(_,oldkey) = _heappop(_heap)
_cache.pop(oldkey)
else:
_cursize += 1
# insert this element
_cache[key] = f(*args)
_heappush(_heap,(_time(),key))
wrapper.misses += 1
else:
wrapper.hits += 1
return cache[key]
wrapper.__doc__ = f.__doc__
wrapper.__name__ = f.__name__
wrapper.hits = wrapper.misses = 0
return wrapper
return decorating_function
"""decorator to 'memoize' a function - caching its results"""
def decorating_function(f):
cache = {} # map from key to value
heap = [] # list of keys, in LRU heap
cursize = 0 # because len() is slow
def wrapper(*args):
key = repr(args)
# performance crap
_cache=cache
_heap=heap
_heappop = heapq.heappop
_heappush = heapq.heappush
_time = time.time
_cursize = cursize
_maxsize = maxsize
if not _cache.has_key(key):
if _cursize == _maxsize:
# pop oldest element
(_,oldkey) = _heappop(_heap)
_cache.pop(oldkey)
else:
_cursize += 1
# insert this element
_cache[key] = f(*args)
_heappush(_heap,(_time(),key))
wrapper.misses += 1
else:
wrapper.hits += 1
return cache[key]
wrapper.__doc__ = f.__doc__
wrapper.__name__ = f.__name__
wrapper.hits = wrapper.misses = 0
return wrapper
return decorating_function
File diff suppressed because it is too large Load Diff
+22 -22
View File
@@ -35,13 +35,13 @@ from service.websrv_lib import HTTPDir,FixSendError
class DAVHandler(FixSendError,DAVRequestHandler):
verbose = False
def get_userinfo(self,user,pw):
print "get_userinfo"
return False
print "get_userinfo"
return False
def _log(self, message):
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
def handle(self):
pass
@@ -49,30 +49,30 @@ class DAVHandler(FixSendError,DAVRequestHandler):
pass
def setup(self):
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
self.IFACE_CLASS = tinydav_handler(self)
pass
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
self.IFACE_CLASS = tinydav_handler(self)
pass
def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
def log_error(self, format, *args):
netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
try:
from service.http_server import reg_http_service,OpenERPAuthProvider
if (config.get_misc('webdav','enable',False)):
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
handler = DAVHandler
handler.verbose = config.get_misc('webdav','verbose',True)
handler.debug = config.get_misc('webdav','debug',True)
reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
from service.http_server import reg_http_service,OpenERPAuthProvider
if (config.get_misc('webdav','enable',False)):
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
handler = DAVHandler
handler.verbose = config.get_misc('webdav','verbose',True)
handler.debug = config.get_misc('webdav','debug',True)
reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
except Exception, e:
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
#eof