Skip to content
Snippets Groups Projects
Commit 62e2a2f2 authored by Harshad Modi's avatar Harshad Modi
Browse files

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

bzr revid: hmo@tinyerp.com-20091224093327-h163qsphpqi78y9q
parents 85b4f8da bc8ede63
Branches
Tags
No related merge requests found
Showing
with 2459 additions and 958 deletions
......@@ -19,7 +19,12 @@
#
##############################################################################
import content_index
import std_index
import document_storage
import document_directory
import directory_content
import directory_report
import document
import ftpserver
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
......@@ -22,27 +22,26 @@
{
'name': 'Integrated Document Management System',
'version': '1.1',
'version': '1.99',
'category': 'Generic Modules/Others',
'description': """This is a complete document management system:
* FTP Interface
'description': """This is a complete document management system:
* User Authentication
* Document Indexation
""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': ['base', 'process'],
'init_xml': ['document_data.xml', 'document_demo.xml'],
'init_xml': [],
'update_xml': [
'document_view.xml',
'document_data.xml',
'security/document_security.xml',
'security/ir.model.access.csv'
],
'demo_xml': [],
'demo_xml': [ 'document_demo.xml',],
'installable': True,
'active': False,
'certificate': '0070515416461',
'certificate': None,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
......@@ -18,53 +18,195 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
import os
import StringIO
import odt2txt
import tempfile
#
# This should be the indexer
#
def _to_unicode(s):
try:
return s.decode('utf-8')
except UnicodeError:
# A quick hack: if netsvc is not there, emulate it. Thus, work offline, too
try:
import netsvc
def log(lvl,msg):
netsvc.Logger().notifyChannel("index",lvl,msg)
except:
class netsvc:
LOG_NOTSET = 'notset'
LOG_DEBUG_RPC = 'debug_rpc'
LOG_DEBUG = 'debug'
LOG_DEBUG2 = 'debug2'
LOG_INFO = 'info'
LOG_WARNING = 'warn'
LOG_ERROR = 'error'
LOG_CRITICAL = 'critical'
def log(lvl,msg):
print msg
class NhException(Exception):
pass
from subprocess import Popen, PIPE
class indexer():
""" An indexer knows how to parse the content of some file.
Typically, one indexer should be instantiated per file
type.
Override this class to add more functionality. Note that
you should only override the Content or the File methods
that give an optimal result. """
def _getMimeTypes(self):
""" Return supported mimetypes """
return []
def _getExtensions(self):
return []
def _getDefMime(self,ext):
""" Return a mimetype for this document type, ideally the
closest to the extension ext. """
mts = self._getMimeTypes();
if len (mts):
return mts[0]
return None
def indexContent(self,content,filename=None, realfile = None):
""" Use either content or the real file, to index.
Some parsers will work better with the actual
content, others parse a file easier. Try the
optimal.
"""
res = ''
try:
return s.decode('latin')
except UnicodeError:
if content != None:
return self._doIndexContent(content)
except NhException:
pass
if realfile != None:
try:
return s.encode('ascii')
except UnicodeError:
return s
return self._doIndexFile(realfile)
except NhException:
pass
fp = open(realfile,'rb')
content2 = fp.read()
fp.close()
# The not-handled exception may be raised here
return self._doIndexContent(content2)
# last try, with a tmp file
if content:
try:
fname,ext = filename and os.path.splitext(filename) or ('','')
fd, rfname = tempfile.mkstemp(suffix=ext)
os.write(fd, content)
os.close(fd)
res = self._doIndexFile(rfname)
os.unlink(rfname)
return res
except NhException:
pass
raise NhException('No appropriate method to index file')
def _doIndexContent(self,content):
raise NhException("Content not handled here")
def _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
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
cntIndex = contentIndex()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=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 base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import netsvc
import os
import nodes
import StringIO
class document_directory_content_type(osv.osv):
_name = 'document.directory.content.type'
_description = 'Directory Content Type'
_columns = {
'name': fields.char('Content Type', size=64, required=True),
'code': fields.char('Extension', size=4),
'active': fields.boolean('Active'),
'mimetype': fields.char('Mime Type',size=32)
}
_defaults = {
'active': lambda *args: 1
}
document_directory_content_type()
class document_directory_content(osv.osv):
_name = 'document.directory.content'
_description = 'Directory Content'
_order = "sequence"
def _extension_get(self, cr, uid, context={}):
cr.execute('select code,name from document_directory_content_type where active')
res = cr.fetchall()
return res
_columns = {
'name': fields.char('Content Name', size=64, required=True),
'sequence': fields.integer('Sequence', size=16),
'prefix': fields.char('Prefix', size=16),
'suffix': fields.char('Suffix', size=16),
'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
'directory_id': fields.many2one('document.directory', 'Directory'),
}
_defaults = {
'extension': lambda *args: '.pdf',
'sequence': lambda *args: 1,
'include_name': lambda *args: 1,
}
def _file_get(self, cr, node, nodename, content, context=None):
""" return the nodes of a <node> parent having a <content> content
The return value MUST be false or a list of node_class objects.
"""
# TODO: respect the context!
model = node.res_model
if content.include_name and not model:
return False
res2 = []
tname = ''
if content.include_name:
content_name = node.displayname or ''
obj = node.context._dirobj.pool.get(model)
if content_name:
tname = (content.prefix or '') + content_name + (content.suffix or '') + (content.extension or '')
else:
tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
if tname.find('/'):
tname=tname.replace('/', '_')
if not nodename:
n = nodes.node_content(tname, node, node.context,content)
res2.append( n)
else:
if nodename == tname:
n = nodes.node_content(tname, node, node.context,content)
n.fill_fields(cr)
res2.append(n)
return res2
def process_write(self, cr, uid, node, data, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
return True
def process_read(self, cr, uid, node, context=None):
if node.extension != '.pdf':
raise Exception("Invalid content: %s" % node.extension)
report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.report_id)
srv = netsvc.Service._services['report.'+report.report_name]
pdf,pdftype = srv.create(cr, uid, [node.context.context['res_id']], {}, {})
return pdf
document_directory_content()
# -*- 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()
This diff is collapsed.
<?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>
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from osv import osv, fields
from osv.orm import except_orm
import urlparse
import os
import nodes
from tools.translate import _
class document_directory(osv.osv):
_name = 'document.directory'
_description = 'Document directory'
_columns = {
'name': fields.char('Name', size=64, required=True, select=1),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'file_type': fields.char('Content Type', size=32),
'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
'user_id': fields.many2one('res.users', 'Owner'),
'storage_id': fields.many2one('document.storage', 'Storage'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'parent_id': fields.many2one('document.directory', 'Parent Item'),
'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
help="Select an object here and Open ERP will create a mapping for each of these " \
"objects, using the given domain, when browsing through FTP."),
'resource_field': fields.char('Name field',size=32,help='Field to be used as name on resource directories. If empty, the "name" will be used.'),
'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
help="If you put an object here, this directory template will appear bellow all of these objects. " \
"Don't put a parent directory if you select a parent model."),
'ressource_id': fields.integer('Resource ID'),
'ressource_tree': fields.boolean('Tree Structure',
help="Check this if you want to use the same tree structure as the object selected in the system."),
'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'),
}
def _get_root_directory(self, cr,uid, context=None):
objid=self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'dir_root')
if not mid:
return None
except Exception, e:
import netsvc
logger = netsvc.Logger()
logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set directory root:'+ str(e))
return None
return objid.browse(cr, uid, mid, context=context).res_id
def _get_def_storage(self,cr,uid,context=None):
if context and context.has_key('default_parent_id'):
# Use the same storage as the parent..
diro = self.browse(cr,uid,context['default_parent_id'])
if diro.storage_id:
return diro.storage_id.id
objid=self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'storage_default')
return objid.browse(cr, uid, mid, context=context).res_id
except Exception:
return None
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'domain': lambda self,cr,uid,ctx: '[]',
'type': lambda *args: 'directory',
'ressource_id': lambda *a: 0,
'parent_id': _get_root_directory,
'storage_id': _get_def_storage,
}
_sql_constraints = [
('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!')
]
def ol_get_resource_path(self,cr,uid,dir_id,res_model,res_id):
# this method will be used in process module
# to be need test and Improvement if resource dir has parent resource (link resource)
path=[]
def _parent(dir_id,path):
parent=self.browse(cr,uid,dir_id)
if parent.parent_id and not parent.ressource_parent_type_id:
_parent(parent.parent_id.id,path)
path.append(parent.name)
else:
path.append(parent.name)
return path
directory=self.browse(cr,uid,dir_id)
model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
if directory:
_parent(dir_id,path)
path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
#user=self.pool.get('res.users').browse(cr,uid,uid)
#return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
# No way we will return the password!
return "ftp://user:pass@host:port/test/this"
return False
def _check_recursion(self, cr, uid, ids):
level = 100
while len(ids):
cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
if not level:
return False
level -= 1
return True
_constraints = [
(_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
]
def __init__(self, *args, **kwargs):
res = super(document_directory, self).__init__(*args, **kwargs)
#self._cache = {}
def onchange_content_id(self, cr, uid, ids, ressource_type_id):
return {}
"""
PRE:
uri: of the form "Sales Order/SO001"
PORT:
uri
object: the object.directory or object.directory.content
object2: the other object linked (if object.directory.content)
"""
def get_object(self, cr, uid, uri, context=None):
""" Return a node object for the given uri.
This fn merely passes the call to node_context
"""
if not context:
context = {}
lang = context.get('lang',False)
if not lang:
user = self.pool.get('res.users').browse(cr, uid, uid)
lang = user.context_lang
context['lang'] = lang
try: #just instrumentation
return nodes.get_node_context(cr, uid, context).get_uri(cr,uri)
except Exception,e:
print "exception: ",e
raise
def _locate_child(self, cr,uid, root_id, uri,nparent, ncontext):
""" try to locate the node in uri,
Return a tuple (node_dir, remaining_path)
"""
did = root_id
duri = uri
path = []
context = ncontext.context
while len(duri):
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
if not nid:
break
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
return (nodes.node_dir(path, nparent,ncontext,self.browse(cr,uid,did, context)), duri)
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','ressource')], context=context)
if nid:
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
return nodes.node_res_dir(path, nparent,ncontext,self.browse(cr,uid,did, context))
# Here, we must find the appropriate non-dir child..
# Chech for files:
fil_obj = self.pool.get('ir.attachment')
nid = fil_obj.search(cr,uid,[('parent_id','=',did),('name','=',duri[0])],context=context)
if nid:
if len(duri)>1:
# cannot treat child as a dir
return None
if len(nid)>1:
print "Duplicate file?",did,duri[0]
path.append(duri[0])
return nodes.node_file(path,nparent,ncontext,fil_obj.browse(cr,uid,nid[0],context))
print "nothing found:",did, duri
#still, nothing found
return None
def old_code():
if not uri:
return node_database(cr, uid, context=context)
turi = tuple(uri)
node = node_class(cr, uid, '/', False, context=context, type='database')
for path in uri[:]:
if path:
node = node.child(path)
if not node:
return False
oo = node.object and (node.object._name, node.object.id) or False
oo2 = node.object2 and (node.object2._name, node.object2.id) or False
return node
def ol_get_childs(self, cr, uid, uri, context={}):
node = self.get_object(cr, uid, uri, context)
if uri:
children = node.children()
else:
children= [node]
result = map(lambda node: node.path_get(), children)
return result
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default ={}
name = self.read(cr, uid, [id])[0]['name']
default.update({'name': name+ " (copy)"})
return super(document_directory,self).copy(cr,uid,id,default,context)
def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
name=vals.get('name',False)
parent_id=vals.get('parent_id',False)
ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
ressource_id=vals.get('ressource_id',0)
if op=='write':
for directory in self.browse(cr,uid,ids):
if not name:
name=directory.name
if not parent_id:
parent_id=directory.parent_id and directory.parent_id.id or False
if not ressource_parent_type_id:
ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
if not ressource_id:
ressource_id=directory.ressource_id and directory.ressource_id or 0
res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
if op=='create':
res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
return True
def write(self, cr, uid, ids, vals, context=None):
if not self._check_duplication(cr,uid,vals,ids,op='write'):
raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
return super(document_directory,self).write(cr,uid,ids,vals,context=context)
def create(self, cr, uid, vals, context=None):
if not self._check_duplication(cr,uid,vals):
raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) :
raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
return super(document_directory,self).create(cr, uid, vals, context)
document_directory()
class document_directory_dctx(osv.osv):
""" In order to evaluate dynamic folders, child items could have a limiting
domain expression. For that, their parents will export a context where useful
information will be passed on.
If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
children could have a domain like [('sale_id', = ,dctx_s_id )]
This system should be used recursively, that is, parent dynamic context will be
appended to all children down the tree.
"""
_name = 'document.directory.dctx'
_description = 'Directory dynamic context'
_columns = {
'dir_id': fields.many2one('document.directory', 'Directory', required=True),
'field': fields.char('Field', size=20, required=True, select=1, help="The name of the field. Note that the prefix \"dctx_\" will be prepended to what is typed here."),
'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
"You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),
}
document_directory_dctx()
class document_directory_node(osv.osv):
_inherit = 'process.node'
_columns = {
'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
}
document_directory_node()
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
#
# Copyright (C) P. Christeas, 2009, all rights reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
import os
import tools
import base64
from tools.misc import ustr
from osv.orm import except_orm
import random
import string
import netsvc
from content_index import cntIndex
""" The algorithm of data storage
We have to consider 3 cases of data /retrieval/:
Given (context,path) we need to access the file (aka. node).
given (directory, context), we need one of its children (for listings, views)
given (ir.attachment, context), we needs its data and metadata (node).
For data /storage/ we have the cases:
Have (ir.attachment, context), we modify the file (save, update, rename etc).
Have (directory, context), we create a file.
Have (path, context), we create or modify a file.
Note that in all above cases, we don't explicitly choose the storage media,
but always require a context to be present.
Note that a node will not always have a corresponding ir.attachment. Dynamic
nodes, for once, won't. Their metadata will be computed by the parent storage
media + directory.
The algorithm says that in any of the above cases, our first goal is to locate
the node for any combination of search criteria. It would be wise NOT to
represent each node in the path (like node[/] + node[/dir1] + node[/dir1/dir2])
but directly jump to the end node (like node[/dir1/dir2]) whenever possible.
We also contain all the parenting loop code in one function. This is intentional,
because one day this will be optimized in the db (Pg 8.4).
"""
def random_name():
random.seed()
d = [random.choice(string.ascii_letters) for x in xrange(10) ]
name = "".join(d)
return name
INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
def create_directory(path):
dir_name = random_name()
path = os.path.join(path,dir_name)
os.makedirs(path)
return dir_name
class document_storage(osv.osv):
""" The primary object for data storage.
Each instance of this object is a storage media, in which our application
can store contents. The object here controls the behaviour of the storage
media.
The referring document.directory-ies will control the placement of data
into the storage.
It is a bad idea to have multiple document.storage objects pointing to
the same tree of filesystem storage.
"""
_name = 'document.storage'
_description = 'Document storage media'
_columns = {
'name': fields.char('Name', size=64, required=True, select=1),
'write_date': fields.datetime('Date Modified', readonly=True),
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
'user_id': fields.many2one('res.users', 'Owner'),
'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
'dir_ids': fields.one2many('document.directory', 'parent_id', 'Directories'),
'type': fields.selection([('db','Database'),('filestore','Internal File storage'),
('realstore','External file storage'), ('virtual','Virtual storage')], 'Type', required=True),
'path': fields.char('Path',size=250,select=1,help="For file storage, the root path of the storage"),
'online': fields.boolean('Online',help="If not checked, media is currently offline and its contents not available", required=True),
'readonly': fields.boolean('Read Only', help="If set, media is for reading only"),
}
def _get_rootpath(self,cr,uid,context=None):
from tools import config
return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
_defaults = {
'user_id': lambda self,cr,uid,ctx: uid,
'online': lambda *args: True,
'readonly': lambda *args: False,
# Note: the defaults below should only be used ONCE for the default
# storage media. All other times, we should create different paths at least.
'type': lambda *args: 'filestore',
'path': _get_rootpath,
}
_sql_constraints = [
# SQL note: a path = NULL doesn't have to be unique.
('path_uniq', 'UNIQUE(type,path)', "The storage path must be unique!")
]
def get_data(self, cr,uid, id, file_node,context = None, fil_obj = None):
""" retrieve the contents of some file_node having storage_id = id
optionally, fil_obj could point to the browse object of the file
(ir.attachment)
"""
if not context:
context = {}
boo = self.browse(cr,uid,id,context)
if fil_obj:
ira = fil_obj
else:
ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
return self.__get_data_3(cr,uid,boo, ira, context)
def __get_data_3(self,cr,uid, boo, ira, context):
if not boo.online:
raise RuntimeError('media offline')
if boo.type == 'filestore':
if not ira.store_fname:
# On a migrated db, some files may have the wrong storage type
# try to fix their directory.
if ira.file_size:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"ir.attachment #%d does not have a filename, but is at filestore, fix it!" %ira.id)
return None
fpath = os.path.join(boo.path,ira.store_fname)
print "Trying to read \"%s\".."% fpath
return file(fpath,'rb').read()
elif boo.type == 'db':
# TODO: we need a better api for large files
if ira.db_datas:
out=base64.decodestring(ira.db_datas)
else:
out=''
return out
elif boo.type == 'realstore':
# fpath = os.path.join(boo.path,
return None
else:
raise TypeError("No %s storage" % boo.type)
def set_data(self, cr,uid, id, file_node, data, context = None, fil_obj = None):
""" store the data.
This function MUST be used from an ir.attachment. It wouldn't make sense
to store things persistently for other types (dynamic).
"""
if not context:
context = {}
boo = self.browse(cr,uid,id,context)
logger = netsvc.Logger()
if fil_obj:
ira = fil_obj
else:
ira = self.pool.get('ir.attachment').browse(cr,uid, file_node.file_id, context=context)
if not boo.online:
raise RuntimeError('media offline')
logger.notifyChannel('document',netsvc.LOG_DEBUG,"Store data for ir.attachment #%d" %ira.id)
store_fname = None
fname = None
if boo.type == 'filestore':
path = boo.path
try:
flag = None
# This can be improved
if os.path.isdir(path):
for dirs in os.listdir(path):
if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
flag = dirs
break
flag = flag or create_directory(path)
filename = random_name()
fname = os.path.join(path, flag, filename)
fp = file(fname,'wb')
fp.write(data)
fp.close()
logger.notifyChannel('document',netsvc.LOG_DEBUG,"Saved data to %s" % fname)
filesize = len(data) # os.stat(fname).st_size
store_fname = os.path.join(flag,filename)
# TODO Here, an old file would be left hanging.
except Exception,e :
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
raise except_orm(_('Error!'), str(e))
elif boo.type == 'db':
filesize = len(data)
# will that work for huge data? TODO
out=base64.encodestring(data)
cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
( out, file_node.file_id ))
else:
raise TypeError("No %s storage" % boo.type)
# 2nd phase: store the metadata
try:
icont = ''
mime = ira.file_type
try:
mime,icont = cntIndex.doIndex(data, ira.datas_fname,
ira.file_type or None,fname)
except Exception,e:
logger.notifyChannel('document', netsvc.LOG_DEBUG, 'Cannot index file: %s' % str(e))
pass
# a hack: /assume/ that the calling write operation will not try
# to write the fname and size, and update them in the db concurrently.
# We cannot use a write() here, because we are already in one.
cr.execute('UPDATE ir_attachment SET store_fname = %s, file_size = %s, index_content = %s, file_type = %s WHERE id = %s',
(store_fname, filesize, ustr(icont), mime, file_node.file_id ))
file_node.content_length = filesize
file_node.content_type = mime
return True
except Exception,e :
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Couldn't save data: %s" %str(e))
# should we really rollback once we have written the actual data?
# at the db case (only), that rollback would be safe
raise except_orm(_('Error at doc write!'), str(e))
def prepare_unlink(self,cr,uid,storage_bo, fil_bo):
""" Before we unlink a file (fil_boo), prepare the list of real
files that have to be removed, too. """
if not storage_bo.online:
raise RuntimeError('media offline')
if storage_bo.type == 'filestore':
fname = fil_bo.store_fname
if not fname:
return None
path = storage_bo.path
return ( storage_bo.id, 'file', os.path.join(path,fname))
elif storage_bo.type == 'db':
return None
else:
raise TypeError("No %s storage" % boo.type)
def do_unlink(self, cr,uid,unres):
for id, ktype, fname in unres:
if ktype == 'file':
try:
os.unlink(fname)
except Exception,e:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Could not remove file %s, please remove manually." % fname)
else:
netsvc.Logger().notifyChannel('document',netsvc.LOG_WARNING,"Unknown unlink key %s" % ktype)
return True
document_storage()
#eof
......@@ -3,6 +3,48 @@
<menuitem name="Document Management" icon="terp-stock" id="menu_document"/>
<menuitem name="Document Configuration" id="menu_document_configuration" parent="menu_document"/>
<record model="ir.ui.view" id="view_document_storage_form">
<field name="name">document.storage</field>
<field name="model">document.storage</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Storage Media">
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="type" />
<field name="online" />
<field name="readonly" />
<field name="path" />
</form>
</field>
</record>
<record model="ir.ui.view" id="view_document_storage_tree">
<field name="name">document.storage</field>
<field name="model">document.storage</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Storage Media" toolbar="1">
<field name="name"/>
<field name="type"/>
<field name="online"/>
<field name="readonly"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_document_storage_form">
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.storage</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
name="Storage Media"
action="action_document_storage_form"
id="menu_document_storage_media"
parent="menu_document_configuration"/>
<record model="ir.ui.view" id="view_document_directory_form">
<field name="name">document.directory</field>
<field name="model">document.directory</field>
......@@ -12,6 +54,7 @@
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="parent_id"/>
<field name="storage_id" />
<notebook colspan="4">
<page string="Definition">
<separator string="Directory Type" colspan="4"/>
......@@ -20,14 +63,17 @@
<newline/>
<field name="domain" attrs="{'required': [('type','=','ressource')], 'readonly': [('type','=','static')]}"/>
<field name="ressource_tree"/>
<field name="resource_field"/>
<field name="ressource_id" select="2" readonly="1"/>
<field name="ressource_parent_type_id"/>
<separator string="Auto-Generated Files" colspan="4"/>
</page>
<page string="Generated Files">
<label colspan="4" string="For each entry here, virtual files will appear in this folder." />
<field name="content_ids" nolabel="1" colspan="4">
<form string="Contents">
<field name="name"/>
<field name="sequence"/>
<field name="prefix"/>
<field name="suffix"/>
<field name="extension"/>
<field name="include_name"/>
......@@ -41,7 +87,20 @@
<field name="extension"/>
</tree>
</field>
</page><page string="Security">
</page>
<page string="Dynamic context">
<field name="dctx_ids" nolabel="1" colspan="4">
<tree string="Fields" editable="bottom">
<field name="field"/>
<field name="expr"/>
</tree>
<form string="Fields">
<field name="field"/>
<field name="expr"/>
</form>
</field>
</page>
<page string="Security">
<field name="group_ids" colspan="4" nolabel="1"/>
</page>
</notebook>
......@@ -87,19 +146,7 @@
<menuitem
action="action_document_directory_tree"
id="menu_document_directories_tree"
parent="menu_document_configuration"/>
<record model="ir.actions.url" id="action_document_browse">
<field name="name">Browse Files</field>
<field name="url">ftp://localhost:8021/</field>
</record>
<menuitem
name="Browse Files Using FTP"
action="action_document_browse"
id="menu_document_browse"
type="url"
icon="STOCK_EXECUTE"
parent="menu_document"/>
parent="menu_document_configuration"/>
<record model="ir.ui.view" id="view_document_file_form">
<field name="name">ir.attachment</field>
......@@ -151,7 +198,7 @@
<group colspan="2" col="2">
<separator string="File Information" colspan="2"/>
<field name="file_size" readonly="1"/>
<field name="parent_id" readonly="1" select="2"/>
<field name="parent_id" select="2"/>
</group>
</page><page string="Notes">
<field colspan="4" name="description" nolabel="1"/>
......@@ -242,87 +289,11 @@
</field>
</field>
</record>
<!-- initial ftp server address, port configuration -->
<record id="view_auto_config_form" model="ir.ui.view">
<field name="name">Auto Configure Directory</field>
<field name="model">document.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Auto Configure">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will automatically configure the document management system according to modules installed on your system." align="0.0" colspan="4"/>
<separator string="" colspan="4"/>
<label string="Choose the ftp server address and port. These values will be stored in the configuration file." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<field name="suggested_host" colspan="4"/>
<field name="port" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
</form>
</field>
</record>
<record id="action_config_auto_directory" model="ir.actions.act_window">
<field name="name">Auto Configure Directory</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.actions.todo" id="config_auto_directory">
<field name="name">Auto Configure Directory</field>
<field name="note">This wizard will configure the URL of the server of the document management system. These ftp server address and port will be stored in the configuration file.</field>
<field name="action_id" ref="action_config_auto_directory"/>
</record>
<!-- ftp server address, port configuration -->
<record id="view_config_form" model="ir.ui.view">
<field name="name">Configure FTP server address</field>
<field name="model">document.configuration.ftp_server.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="FTP Server Configuration">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will configure the ftp server address and port." align="0.0" colspan="4"/>
<label string="These values will be stored in the configuration file." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<field name="suggested_host" colspan="4"/>
<field name="port" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
<group col="4" colspan="4">
<label string="Don't forget to restart the Open ERP server if you change these values !" align="0.0" colspan="4"/>
</group>
</form>
</field>
</record>
<record id="action_config_ftp_server" model="ir.actions.act_window">
<field name="name">Configure FTP server address</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.ftp_server.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="FTP Server Configuration"
id="menu_document_configuration_ftpserver"
action="action_config_ftp_server"
parent="menu_document_configuration"/>
<act_window domain="[('partner_id', '=', active_id)]"
id="act_res_partner_document" name="Related Documents"
res_model="ir.attachment"
src_model="res.partner"/>
</data>
</openerp>
......
This diff is collapsed.
#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
......
......@@ -5,7 +5,5 @@
"access_document_directory_content_all","document.directory.content all","model_document_directory_content",,1,0,0,0
"access_document_directory_content_group_document_manager","document.directory.content document manager","model_document_directory_content","group_document_manager",1,1,1,1
"access_document_directory_content_group_system","document.directory.content group system","model_document_directory_content","base.group_system",1,1,1,1
"access_document_configuation_wizard","document.configuration.wizard document manager","model_document_configuration_wizard","group_document_manager",1,1,1,1
"access_document_configuation_wizard_sytem","document.configuration.wizard group system","model_document_configuration_wizard","base.group_system",1,1,1,1
"access_document_directory_content_type_group_document_manager","document.directory.content.type document manager","model_document_directory_content_type","group_document_manager",1,1,1,1
"access_document_directory_content_type_group_system","document.directory.content.type group system","model_document_directory_content_type","base.group_user",1,0,0,0
# -*- 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
#!/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
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import doc_conf_wizard
import ftpserver
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Integrated FTP Server with Document Management System',
'version': '1.99',
'category': 'Generic Modules/Others',
'description': """This is a support FTP Interface with document management system:
""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': ['base', 'document'],
'init_xml': [],
'update_xml': [
'document_ftp_view.xml',
'security/ir.model.access.csv'
],
'demo_xml': [],
'installable': True,
'active': False,
'certificate': None,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# -*- 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()
<openerp>
<data>
<record model="ir.actions.url" id="action_document_browse">
<field name="name">Browse Files</field>
<field name="url">ftp://localhost:8021/</field>
</record>
<menuitem
name="Browse Files Using FTP"
action="action_document_browse"
id="menu_document_browse"
type="url"
icon="STOCK_EXECUTE"
parent="document.menu_document"/>
<record id="view_auto_config_form" model="ir.ui.view">
<field name="name">Auto Configure Directory</field>
<field name="model">document.configuration.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Auto Configure">
<separator string="Document Management System." colspan="4"/>
<label string="This wizard will automatically configure the document management system according to modules installed on your system." align="0.0" colspan="4"/>
<field name="host" colspan="4"/>
<separator string="" colspan="4"/>
<label string="" colspan="2"/>
<group col="4" colspan="2">
<button special="cancel" string="Cancel" name="action_cancel" type="object" icon='gtk-cancel'/>
<button name="action_config" string="Configure" icon='gtk-ok' type="object"/>
</group>
</form>
</field>
</record>
<record id="action_config_auto_directory" model="ir.actions.act_window">
<field name="name">Auto Configure Directory</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.configuration.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.actions.todo" id="config_auto_directory">
<field name="name">Auto Configure Directory</field>
<field name="note">This wizard will configure the URL of the server of the document management system.</field>
<field name="action_id" ref="action_config_auto_directory"/>
</record>
</data>
</openerp>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment