[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:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
@@ -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
|
||||
Executable
+46
@@ -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
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
+199
-153
@@ -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())
|
||||
+2
-24
@@ -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)
|
||||
+578
-795
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
|
||||
|
@@ -19,6 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import document
|
||||
import document_ics
|
||||
import document_ics_config_wizard
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+506
-509
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user