From d5a68a7b692a6fa568c5fcebc580109afb5bb6d4 Mon Sep 17 00:00:00 2001 From: p_christeas <> Date: Wed, 2 Dec 2009 11:06:57 +0530 Subject: [PATCH] [IMP]document : get improvement of document from xrg-addons branch who made nice improvement in document bzr revid: hmo@tinyerp.com-20091202053657-4oflezyhbolu3xvo --- addons/document/__init__.py | 7 + addons/document/__terp__.py | 8 +- addons/document/content_index.py | 228 +++- addons/document/directory_content.py | 113 ++ addons/document/directory_report.py | 64 + addons/document/doc_conf_wizard.py | 163 +++ addons/document/document.py | 776 +---------- addons/document/document_data.xml | 104 +- addons/document/document_demo.xml | 84 -- addons/document/document_directory.py | 312 +++++ addons/document/document_storage.py | 281 ++++ addons/document/document_view.xml | 178 +-- addons/document/ftpserver/__init__.py | 5 +- addons/document/ftpserver/abstracted_fs.py | 37 +- addons/document/ftpserver/authorizer.py | 26 +- addons/document/ftpserver/ftpserver.py | 1375 +++++++++----------- addons/document/nodes.py | 776 +++++++++++ addons/document/odt2txt.py | 1 + addons/document/std_index.py | 111 ++ addons/document/test_cindex.py | 46 + 20 files changed, 2909 insertions(+), 1786 deletions(-) create mode 100644 addons/document/directory_content.py create mode 100644 addons/document/directory_report.py create mode 100644 addons/document/doc_conf_wizard.py create mode 100644 addons/document/document_directory.py create mode 100644 addons/document/document_storage.py create mode 100644 addons/document/nodes.py create mode 100644 addons/document/std_index.py create mode 100755 addons/document/test_cindex.py diff --git a/addons/document/__init__.py b/addons/document/__init__.py index 5c97428f05d..a9810a8b7b8 100644 --- a/addons/document/__init__.py +++ b/addons/document/__init__.py @@ -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 diff --git a/addons/document/__terp__.py b/addons/document/__terp__.py index d2246d270bf..be9eb6a9d3f 100644 --- a/addons/document/__terp__.py +++ b/addons/document/__terp__.py @@ -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: diff --git a/addons/document/content_index.py b/addons/document/content_index.py index 6c0f083b8ac..a063251c698 100644 --- a/addons/document/content_index.py +++ b/addons/document/content_index.py @@ -18,53 +18,195 @@ # along with this program. If not, see . # ############################################################################## - -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: diff --git a/addons/document/directory_content.py b/addons/document/directory_content.py new file mode 100644 index 00000000000..4ccad653711 --- /dev/null +++ b/addons/document/directory_content.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## +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 parent having a 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() diff --git a/addons/document/directory_report.py b/addons/document/directory_report.py new file mode 100644 index 00000000000..b5a80adc7ec --- /dev/null +++ b/addons/document/directory_report.py @@ -0,0 +1,64 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). 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 . +# +############################################################################## + +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() diff --git a/addons/document/doc_conf_wizard.py b/addons/document/doc_conf_wizard.py new file mode 100644 index 00000000000..66354c8876b --- /dev/null +++ b/addons/document/doc_conf_wizard.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +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() diff --git a/addons/document/document.py b/addons/document/document.py index cd3d667f8bf..1dffba0453d 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -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() - diff --git a/addons/document/document_data.xml b/addons/document/document_data.xml index 511de005b6f..77c80b078d1 100644 --- a/addons/document/document_data.xml +++ b/addons/document/document_data.xml @@ -1,10 +1,112 @@ - + .pdf PDF Report + application/pdf + + + + Default File storage + + + + + DB storage + + + + + + + Documents + + + 0 + + + + My Folder + + + + 0 + + + + + Partners by Category + + ressource + 1 + + 0 + + + + + + + Partners + ressource + [('category_id','in',[active_id])] + + + + 0 + + + + + Personnal Folders + + ressource + + + 0 + + + + + Products + + + 0 + + + + + Sales Order + + + 0 + + + + + All Sales Order + + + 0 + + + + + Quotations + + + 0 + + + + + Projects + + + 0 + diff --git a/addons/document/document_demo.xml b/addons/document/document_demo.xml index 95910e6031b..1c757a73909 100644 --- a/addons/document/document_demo.xml +++ b/addons/document/document_demo.xml @@ -2,90 +2,6 @@ - - Documents - - 0 - - - - My Folder - - - 0 - - - - - Partners by Category - - ressource - 1 - 0 - - - - - - - Partners - ressource - [('category_id','in',[active_id])] - - - - 0 - - - - - Personnal Folders - - ressource - - 0 - - - - - Products - - - 0 - - - - - Sales Order - - - 0 - - - - - All Sales Order - - - 0 - - - - - Quotations - - - 0 - - - - - Projects - - - 0 - - diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py new file mode 100644 index 00000000000..133927f9664 --- /dev/null +++ b/addons/document/document_directory.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +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() diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py new file mode 100644 index 00000000000..d4096138800 --- /dev/null +++ b/addons/document/document_storage.py @@ -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 . +# +############################################################################## + +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 diff --git a/addons/document/document_view.xml b/addons/document/document_view.xml index 6e570bb406a..babc5af33b3 100644 --- a/addons/document/document_view.xml +++ b/addons/document/document_view.xml @@ -3,6 +3,48 @@ + + document.storage + document.storage + form + +
+ + + + + + + + +
+ + + document.storage + document.storage + tree + + + + + + + + + + + + ir.actions.act_window + document.storage + form + tree,form + + + document.directory document.directory @@ -12,6 +54,7 @@ + @@ -20,14 +63,17 @@ + - - + + + @@ -151,7 +210,7 @@ - + @@ -243,86 +302,45 @@ - - - Auto Configure Directory - document.configuration.wizard - form - -
- -