[IMP]document : get improvement of document from xrg-addons branch who made nice improvement in document
bzr revid: hmo@tinyerp.com-20091202053657-4oflezyhbolu3xvo
This commit is contained in:
committed by
Harry (Open ERP)
parent
8d0b4a7418
commit
d5a68a7b69
@@ -19,6 +19,13 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import content_index
|
||||
import std_index
|
||||
import document_storage
|
||||
import document_directory
|
||||
import directory_content
|
||||
import directory_report
|
||||
import doc_conf_wizard
|
||||
import document
|
||||
import ftpserver
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
{
|
||||
'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
|
||||
@@ -32,17 +32,17 @@
|
||||
'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:
|
||||
try:
|
||||
return s.decode('latin')
|
||||
except UnicodeError:
|
||||
try:
|
||||
return s.encode('ascii')
|
||||
except UnicodeError:
|
||||
return s
|
||||
# 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
|
||||
|
||||
|
||||
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
|
||||
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:
|
||||
if content != None:
|
||||
return self._doIndexContent(content)
|
||||
except NhException:
|
||||
pass
|
||||
|
||||
if realfile != None:
|
||||
try:
|
||||
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 _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 os
|
||||
import nodes
|
||||
|
||||
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!
|
||||
if content.include_name and not node.object2:
|
||||
return False
|
||||
|
||||
res2 = []
|
||||
tname = ''
|
||||
if content.include_name:
|
||||
content_name = node.object2.name
|
||||
obj = pool.get(node.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,'')
|
||||
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):
|
||||
if node.extension != '.pdf':
|
||||
raise Exception("Invalid content: %s" % node.extension)
|
||||
return True
|
||||
def process_read(self, cr, uid, node):
|
||||
if node.extension != '.pdf':
|
||||
raise Exception("Invalid content: %s" % node.extension)
|
||||
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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
+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'),
|
||||
}
|
||||
_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),
|
||||
'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,312 @@
|
||||
# -*- 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
|
||||
|
||||
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,281 @@
|
||||
# -*- 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
|
||||
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>
|
||||
@@ -151,7 +210,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"/>
|
||||
@@ -243,86 +302,45 @@
|
||||
</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="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 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>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
@@ -28,7 +28,7 @@ import netsvc
|
||||
from tools import config
|
||||
from tools.misc import detect_ip_addr
|
||||
HOST = ''
|
||||
PORT = int(config.get('ftp_server_port', 8021))
|
||||
PORT = 8021
|
||||
PASSIVE_PORTS = None
|
||||
pps = config.get('ftp_server_passive_ports', '').split(':')
|
||||
if len(pps) == 2:
|
||||
@@ -53,8 +53,7 @@ class ftp_server(threading.Thread):
|
||||
ftpserver.logerror = lambda msg: self.log(netsvc.LOG_ERROR, msg)
|
||||
|
||||
HOST = config.get('ftp_server_address', detect_ip_addr())
|
||||
PORT = int(config.get('ftp_server_port', '8021'))
|
||||
|
||||
PORT = int(config.get('ftp_server_port', '8021'))
|
||||
address = (HOST, PORT)
|
||||
ftpd = ftpserver.FTPServer(address, ftpserver.FTPHandler)
|
||||
ftpd.serve_forever()
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# -*- encoding: utf-8 -*-
|
||||
import os
|
||||
import time
|
||||
from tarfile import filemode
|
||||
@@ -141,8 +121,8 @@ class abstracted_fs:
|
||||
# Ok
|
||||
def db_list(self):
|
||||
#return pooler.pool_dic.keys()
|
||||
s = netsvc.LocalService('db')
|
||||
result = s.list()
|
||||
s = netsvc.ExportService.getService('db')
|
||||
result = s.exp_list()
|
||||
self.db_name_list = []
|
||||
for db_name in result:
|
||||
db, cr = None, None
|
||||
@@ -222,13 +202,18 @@ class abstracted_fs:
|
||||
|
||||
# Ok
|
||||
def fs2ftp(self, node):
|
||||
res='/'
|
||||
res = '/'
|
||||
if node:
|
||||
res=os.path.normpath(node.path)
|
||||
path = node.path
|
||||
print '**********', path
|
||||
if isinstance(path,list):
|
||||
path = '/'.join(path)
|
||||
res = os.path.normpath(path)
|
||||
res = res.replace("\\", "/")
|
||||
while res[:2] == '//':
|
||||
res = res[1:]
|
||||
res='/' + node.cr.dbname + '/' + _to_decode(res)
|
||||
if len(node.path):
|
||||
res = '/' + node.path[0] + '/' + _to_decode(res)
|
||||
|
||||
#res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/'
|
||||
return res
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import pooler
|
||||
from service import security
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
class authorizer:
|
||||
read_perms = "elr"
|
||||
@@ -69,7 +47,7 @@ class authorizer:
|
||||
paths = path.split('/')
|
||||
if not len(paths)>2:
|
||||
return True
|
||||
db_name = paths[1]
|
||||
db_name = paths[1]
|
||||
db,pool = pooler.get_db_and_pool(db_name)
|
||||
res = security.login(db_name, username, self.password)
|
||||
return bool(res)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,776 @@
|
||||
# -*- 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.cr = cr
|
||||
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):
|
||||
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
|
||||
|
||||
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
|
||||
# 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,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.model
|
||||
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 = safe_eval(dirr.domain,self.dctx)
|
||||
# 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 []
|
||||
uid = self.context.uid
|
||||
ctx = self.context.context.copy()
|
||||
ctx.update(self.dctx)
|
||||
where = []
|
||||
if self.domain:
|
||||
where.append(self.domain)
|
||||
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
|
||||
|
||||
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,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, 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 = parent.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
|
||||
|
||||
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 parent.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)
|
||||
where = [('parent_id','=',self.dir_id) ]
|
||||
if name:
|
||||
where.append(('name','=',name))
|
||||
ids = dirobj.search(cr, uid, where,context=ctx)
|
||||
res = []
|
||||
if ids:
|
||||
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
|
||||
if dirr.type == 'directory':
|
||||
res.append(node_res_obj(dirr.name,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))
|
||||
|
||||
fil_obj=dirobj.pool.get('ir.attachment')
|
||||
where2 = where + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
|
||||
# print "where clause for dir_obj", where2
|
||||
ids = fil_obj.search(cr,uid,where2,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,
|
||||
'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 -*-
|
||||
##############################################################################
|
||||
#
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user