@@ -53,7 +53,7 @@
|
||||
],
|
||||
'demo_xml': [ 'document_demo.xml','board_document_demo.xml'],
|
||||
'test': [
|
||||
'test/document_test.yml',
|
||||
'test/document_test2.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<menuitem id="menu_reporting" name="Reporting" sequence="2" parent="knowledge.menu_document"/>
|
||||
<menuitem
|
||||
name="Dashboard"
|
||||
id="menu_board_document"
|
||||
id="menu_reports_document"
|
||||
parent="menu_reporting"
|
||||
sequence="0"
|
||||
icon="terp-graph"
|
||||
@@ -61,10 +61,10 @@
|
||||
|
||||
|
||||
<menuitem
|
||||
parent="menu_board_document"
|
||||
parent="menu_reports_document"
|
||||
action="open_board_document_manager"
|
||||
sequence="1"
|
||||
id="menu_board_document_manager"
|
||||
id="menu_reports_document_manager"
|
||||
icon="terp-graph"/>
|
||||
|
||||
<record model="ir.ui.view" id="board_document_manager_form1">
|
||||
@@ -107,10 +107,10 @@
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
parent="menu_board_document"
|
||||
parent="menu_reports_document"
|
||||
action="open_board_document_manager1"
|
||||
sequence="1"
|
||||
id="menu_board_document_manager1"
|
||||
id="menu_reports_document_manager1"
|
||||
icon="terp-graph"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -153,7 +153,10 @@ class contentIndex(object):
|
||||
if realfname :
|
||||
fname = realfname
|
||||
else:
|
||||
bname,ext = os.path.splitext(filename)
|
||||
try:
|
||||
bname,ext = os.path.splitext(filename or 'test.tmp')
|
||||
except Exception:
|
||||
bname, ext = filename, 'tmp'
|
||||
fd, fname = tempfile.mkstemp(suffix=ext)
|
||||
os.write(fd, content)
|
||||
os.close(fd)
|
||||
|
||||
+83
-48
@@ -28,6 +28,7 @@ import os
|
||||
|
||||
import pooler
|
||||
import netsvc
|
||||
from osv.orm import except_orm
|
||||
#import StringIO
|
||||
|
||||
from psycopg2 import Binary
|
||||
@@ -72,33 +73,32 @@ class document_file(osv.osv):
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'user_id': fields.many2one('res.users', 'Owner', select=1),
|
||||
'group_ids': fields.many2many('res.groups', 'document_group_rel', 'item_id', 'group_id', 'Groups'),
|
||||
# the directory id now is mandatory. It can still be computed automatically.
|
||||
'parent_id': fields.many2one('document.directory', 'Directory', select=1),
|
||||
'file_size': fields.integer('File Size', required=True),
|
||||
'file_type': fields.char('Content Type', size=128),
|
||||
# Columns from ir.attachment:
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
|
||||
'write_date': fields.datetime('Date Modified', readonly=True),
|
||||
'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
|
||||
'res_model': fields.char('Attached Model', size=64, readonly=True),
|
||||
'res_id': fields.integer('Attached ID', readonly=True),
|
||||
|
||||
# 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", nodrop=True),
|
||||
'url': fields.char('File URL',size=64),
|
||||
'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
|
||||
'partner_id':fields.many2one('res.partner', 'Partner', select=1),
|
||||
'type':fields.selection([
|
||||
('url','URL'),
|
||||
('binary','Binary'),
|
||||
|
||||
],'Type', help="Type is used to separate URL and binary File"),
|
||||
# Fields of document:
|
||||
'user_id': fields.many2one('res.users', 'Owner', select=1),
|
||||
# 'group_ids': fields.many2many('res.groups', 'document_group_rel', 'item_id', 'group_id', 'Groups'),
|
||||
# the directory id now is mandatory. It can still be computed automatically.
|
||||
'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True),
|
||||
'index_content': fields.text('Indexed Content'),
|
||||
'partner_id':fields.many2one('res.partner', 'Partner', select=1),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'file_size': fields.integer('File Size', required=True),
|
||||
'file_type': fields.char('Content Type', size=128),
|
||||
|
||||
# fields used for file storage
|
||||
'store_fname': fields.char('Stored Filename', size=200),
|
||||
}
|
||||
|
||||
def __get_def_directory(self, cr, uid, context=None):
|
||||
@@ -109,8 +109,6 @@ class document_file(osv.osv):
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
|
||||
'user_id': lambda self, cr, uid, ctx:uid,
|
||||
'file_size': lambda self, cr, uid, ctx:0,
|
||||
'store_method': lambda *args: 'db',
|
||||
'type': 'binary',
|
||||
'parent_id': __get_def_directory
|
||||
}
|
||||
_sql_constraints = [
|
||||
@@ -122,7 +120,7 @@ class document_file(osv.osv):
|
||||
res_model = vals.get('res_model', False)
|
||||
res_id = vals.get('res_id', 0)
|
||||
if op == 'write':
|
||||
for file in self.browse(cr, uid, ids):
|
||||
for file in self.browse(cr, uid, ids): # FIXME fields_only
|
||||
if not name:
|
||||
name = file.name
|
||||
if not parent_id:
|
||||
@@ -156,8 +154,41 @@ class document_file(osv.osv):
|
||||
return False
|
||||
if not self._check_duplication(cr, uid, vals, ids, 'write'):
|
||||
raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
|
||||
result = super(document_file, self).write(cr, uid, ids, vals, context=context)
|
||||
cr.commit()
|
||||
if ('parent_id' in vals) or ('name' in vals):
|
||||
# perhaps this file is renaming or changing directory
|
||||
nctx = nodes.get_node_context(cr,uid,context)
|
||||
dirobj = self.pool.get('document.directory')
|
||||
if 'parent_id' in vals:
|
||||
dbro = dirobj.browse(cr, uid, vals['parent_id'], context=context)
|
||||
dnode = nctx.get_dir_node(cr, dbro)
|
||||
else:
|
||||
dbro = None
|
||||
dnode = None
|
||||
ids2 = []
|
||||
result = False
|
||||
for fbro in self.browse(cr, uid, ids, context=context):
|
||||
if ('parent_id' not in vals or fbro.parent_id.id == vals['parent_id']) \
|
||||
and ('name' not in vals or fbro.name == vals['name']) :
|
||||
ids2.append(fbro.id)
|
||||
continue
|
||||
fnode = nctx.get_file_node(cr, fbro)
|
||||
res = fnode.move_to(cr, dnode or fnode.parent, vals.get('name', fbro.name), fbro, dbro, True)
|
||||
if isinstance(res, dict):
|
||||
vals2 = vals.copy()
|
||||
vals2.update(res)
|
||||
wid = res.get('id', fbro.id)
|
||||
result = super(document_file,self).write(cr,uid,wid,vals2,context=context)
|
||||
# TODO: how to handle/merge several results?
|
||||
elif res == True:
|
||||
ids2.append(fbro.id)
|
||||
elif res == False:
|
||||
pass
|
||||
ids = ids2
|
||||
if 'file_size' in vals: # only write that field using direct SQL calls
|
||||
del vals['file_size']
|
||||
if len(ids) and len(vals):
|
||||
result = super(document_file,self).write(cr, uid, ids, vals, context=context)
|
||||
cr.commit() # ?
|
||||
return result
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
@@ -170,26 +201,10 @@ class document_file(osv.osv):
|
||||
vals['res_id'] = context.get('default_res_id', False)
|
||||
if not vals.get('res_model', False) and context.get('default_res_model', False):
|
||||
vals['res_model'] = context.get('default_res_model', False)
|
||||
if vals.get('res_id', False) and vals.get('res_model', False):
|
||||
obj_model = self.pool.get(vals['res_model'])
|
||||
result = obj_model.read(cr, uid, [vals['res_id']], ['name', 'partner_id', 'address_id'], context=context)
|
||||
if len(result):
|
||||
obj = result[0]
|
||||
if obj_model._name == 'res.partner':
|
||||
vals['partner_id'] = obj['id']
|
||||
elif obj.get('address_id', False):
|
||||
if isinstance(obj['address_id'], tuple) or isinstance(obj['address_id'], list):
|
||||
address_id = obj['address_id'][0]
|
||||
else:
|
||||
address_id = obj['address_id']
|
||||
address = self.pool.get('res.partner.address').read(cr, uid, [address_id], context=context)
|
||||
if len(address):
|
||||
vals['partner_id'] = address[0]['partner_id'][0] or False
|
||||
elif obj.get('partner_id', False):
|
||||
if isinstance(obj['partner_id'], tuple) or isinstance(obj['partner_id'], list):
|
||||
vals['partner_id'] = obj['partner_id'][0]
|
||||
else:
|
||||
vals['partner_id'] = obj['partner_id']
|
||||
if vals.get('res_id', False) and vals.get('res_model', False) \
|
||||
and not vals.get('partner_id', False):
|
||||
vals['partner_id'] = self.__get_partner_id(cr, uid, \
|
||||
vals['res_model'], vals['res_id'], context)
|
||||
|
||||
datas = None
|
||||
if vals.get('link', False) :
|
||||
@@ -198,13 +213,33 @@ class document_file(osv.osv):
|
||||
else:
|
||||
datas = vals.get('datas', False)
|
||||
|
||||
vals['file_size'] = datas and len(datas) or 0
|
||||
if datas:
|
||||
vals['file_size'] = len(datas)
|
||||
else:
|
||||
if vals.get('file_size'):
|
||||
del vals['file_size']
|
||||
if not self._check_duplication(cr, uid, vals):
|
||||
raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
|
||||
result = super(document_file, self).create(cr, uid, vals, context)
|
||||
cr.commit()
|
||||
cr.commit() # ?
|
||||
return result
|
||||
|
||||
def __get_partner_id(self, cr, uid, res_model, res_id, context):
|
||||
""" A helper to retrieve the associated partner from any res_model+id
|
||||
It is a hack that will try to discover if the mentioned record is
|
||||
clearly associated with a partner record.
|
||||
"""
|
||||
obj_model = self.pool.get(res_model)
|
||||
if obj_model._name == 'res.partner':
|
||||
return res_id
|
||||
elif 'partner_id' in obj_model._columns and obj_model._columns['partner_id']._obj == 'res.partner':
|
||||
bro = obj_model.browse(self, cr, uid, res_id, context=context)
|
||||
return bro.partner_id.id
|
||||
elif 'address_id' in obj_model._columns and obj_model._columns['address_id']._obj == 'res.partner.address':
|
||||
bro = obj_model.browse(self, cr, uid, res_id, context=context)
|
||||
return bro.address_id.partner_id.id
|
||||
return False
|
||||
|
||||
def unlink(self, cr, uid, ids, context={}):
|
||||
stor = self.pool.get('document.storage')
|
||||
unres = []
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</record>
|
||||
|
||||
<record model="document.storage" id="storage_default">
|
||||
<field name="name">Default DB storage</field>
|
||||
<field name="name">Database storage</field>
|
||||
<field name="type">db</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
</record>
|
||||
@@ -17,7 +17,7 @@
|
||||
<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="storage_id" ref="storage_default"/>
|
||||
<field name="ressource_id">0</field>
|
||||
</record>
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<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>
|
||||
@@ -35,7 +34,6 @@
|
||||
<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')]" />
|
||||
@@ -57,7 +55,6 @@
|
||||
<field name="name">Personal 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>
|
||||
|
||||
|
||||
@@ -39,19 +39,23 @@ class document_directory(osv.osv):
|
||||
'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'),
|
||||
'parent_id': fields.many2one('document.directory', 'Parent Directory', select=1),
|
||||
'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."),
|
||||
'type': fields.selection([
|
||||
('directory','Static Directory'),
|
||||
('ressource','Folders per resource'),
|
||||
],
|
||||
'Type', required=True, select=1,
|
||||
help="Defines directory's behaviour."),
|
||||
|
||||
'ressource_type_id': fields.many2one('ir.model', 'Resource model',
|
||||
help="Select an object here and there will be one folder per record of that resource."),
|
||||
'resource_field': fields.many2one('ir.model.fields', 'Name field', 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. " \
|
||||
@@ -60,7 +64,7 @@ class document_directory(osv.osv):
|
||||
'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'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +106,8 @@ class document_directory(osv.osv):
|
||||
}
|
||||
_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!')
|
||||
('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!'),
|
||||
('dir_parented', 'check(parent_id IS NOT NULL OR storage_id IS NOT NULL)', 'Directory must have a parent or a storage')
|
||||
]
|
||||
def name_get(self, cr, uid, ids, context={}):
|
||||
res = []
|
||||
@@ -190,17 +195,8 @@ class document_directory(osv.osv):
|
||||
"""
|
||||
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
|
||||
return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
|
||||
|
||||
|
||||
def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
|
||||
@@ -274,6 +270,8 @@ class document_directory(osv.osv):
|
||||
raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
|
||||
return super(document_directory,self).create(cr, uid, vals, context)
|
||||
|
||||
# TODO def unlink(...
|
||||
|
||||
document_directory()
|
||||
|
||||
class document_directory_dctx(osv.osv):
|
||||
|
||||
@@ -24,7 +24,10 @@ from osv import osv, fields
|
||||
import os
|
||||
import tools
|
||||
import base64
|
||||
import errno
|
||||
import logging
|
||||
from StringIO import StringIO
|
||||
import psycopg2
|
||||
|
||||
from tools.misc import ustr
|
||||
from tools.translate import _
|
||||
@@ -33,7 +36,9 @@ from osv.orm import except_orm
|
||||
|
||||
import random
|
||||
import string
|
||||
import pooler
|
||||
import netsvc
|
||||
import nodes
|
||||
from content_index import cntIndex
|
||||
|
||||
DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config.get('root_path'), 'filestore'))
|
||||
@@ -84,6 +89,127 @@ def create_directory(path):
|
||||
os.makedirs(path)
|
||||
return dir_name
|
||||
|
||||
class nodefd_file(nodes.node_descriptor):
|
||||
""" A descriptor to a real file
|
||||
|
||||
Inheriting directly from file doesn't work, since file exports
|
||||
some read-only attributes (like 'name') that we don't like.
|
||||
"""
|
||||
def __init__(self, parent, path, mode):
|
||||
nodes.node_descriptor.__init__(self, parent)
|
||||
self.__file = open(path, mode)
|
||||
|
||||
for attr in ('closed', 'read', 'write', 'seek', 'tell'):
|
||||
setattr(self,attr, getattr(self.__file, attr))
|
||||
|
||||
def close(self):
|
||||
# TODO: locking in init, close()
|
||||
self.__file.close()
|
||||
|
||||
|
||||
class nodefd_db(StringIO, nodes.node_descriptor):
|
||||
""" A descriptor to db data
|
||||
"""
|
||||
def __init__(self, parent, ira_browse, mode):
|
||||
nodes.node_descriptor.__init__(self, parent)
|
||||
if mode.endswith('b'):
|
||||
mode = mode[:-1]
|
||||
|
||||
if mode in ('r', 'r+'):
|
||||
cr = ira_browse._cr # reuse the cursor of the browse object, just now
|
||||
cr.execute('SELECT db_datas FROM ir_attachment WHERE id = %s',(ira_browse.id,))
|
||||
data = cr.fetchone()[0]
|
||||
StringIO.__init__(self, data)
|
||||
elif mode in ('w', 'w+'):
|
||||
StringIO.__init__(self, None)
|
||||
# at write, we start at 0 (= overwrite), but have the original
|
||||
# data available, in case of a seek()
|
||||
elif mode == 'a':
|
||||
StringIO.__init__(self, None)
|
||||
else:
|
||||
logging.getLogger('document.storage').error("Incorrect mode %s specified", mode)
|
||||
raise IOError(errno.EINVAL, "Invalid file mode")
|
||||
self.mode = mode
|
||||
|
||||
def close(self):
|
||||
# we now open a *separate* cursor, to update the data.
|
||||
# FIXME: this may be improved, for concurrency handling
|
||||
par = self._get_parent()
|
||||
uid = par.context.uid
|
||||
cr = pooler.get_db(par.context.dbname).cursor()
|
||||
try:
|
||||
if self.mode in ('w', 'w+', 'r+'):
|
||||
data = self.getvalue()
|
||||
out = psycopg2.Binary(data)
|
||||
cr.execute("UPDATE ir_attachment SET db_datas = %s, file_size=%s WHERE id = %s",
|
||||
(out, len(data), par.file_id))
|
||||
elif self.mode == 'a':
|
||||
data = self.getvalue()
|
||||
out = psycopg2.Binary(data)
|
||||
cr.execute("UPDATE ir_attachment " \
|
||||
"SET db_datas = COALESCE(db_datas,'') || %s, " \
|
||||
" file_size = COALESCE(file_size, 0) + %s " \
|
||||
" WHERE id = %s",
|
||||
(out, len(data), par.file_id))
|
||||
cr.commit()
|
||||
except Exception, e:
|
||||
logging.getLogger('document.storage').exception('Cannot update db file #%d for close:', par.file_id)
|
||||
raise
|
||||
finally:
|
||||
cr.close()
|
||||
StringIO.close(self)
|
||||
|
||||
class nodefd_db64(StringIO, nodes.node_descriptor):
|
||||
""" A descriptor to db data, base64 (the old way)
|
||||
|
||||
It stores the data in base64 encoding at the db. Not optimal, but
|
||||
the transparent compression of Postgres will save the day.
|
||||
"""
|
||||
def __init__(self, parent, ira_browse, mode):
|
||||
nodes.node_descriptor.__init__(self, parent)
|
||||
if mode.endswith('b'):
|
||||
mode = mode[:-1]
|
||||
|
||||
if mode in ('r', 'r+'):
|
||||
StringIO.__init__(self, base64.decodestring(ira_browse.db_datas))
|
||||
elif mode in ('w', 'w+'):
|
||||
StringIO.__init__(self, None)
|
||||
# at write, we start at 0 (= overwrite), but have the original
|
||||
# data available, in case of a seek()
|
||||
elif mode == 'a':
|
||||
StringIO.__init__(self, None)
|
||||
else:
|
||||
logging.getLogger('document.storage').error("Incorrect mode %s specified", mode)
|
||||
raise IOError(errno.EINVAL, "Invalid file mode")
|
||||
self.mode = mode
|
||||
|
||||
def close(self):
|
||||
# we now open a *separate* cursor, to update the data.
|
||||
# FIXME: this may be improved, for concurrency handling
|
||||
par = self._get_parent()
|
||||
uid = par.context.uid
|
||||
cr = pooler.get_db(par.context.dbname).cursor()
|
||||
try:
|
||||
if self.mode in ('w', 'w+', 'r+'):
|
||||
out = self.getvalue()
|
||||
cr.execute('UPDATE ir_attachment SET db_datas = %s::bytea, file_size=%s WHERE id = %s',
|
||||
(base64.encodestring(out), len(out), par.file_id))
|
||||
elif self.mode == 'a':
|
||||
out = self.getvalue()
|
||||
# Yes, we're obviously using the wrong representation for storing our
|
||||
# data as base64-in-bytea
|
||||
cr.execute("UPDATE ir_attachment " \
|
||||
"SET db_datas = encode( (COALESCE(decode(encode(db_datas,'escape'),'base64'),'') || decode(%s, 'base64')),'base64')::bytea , " \
|
||||
" file_size = COALESCE(file_size, 0) + %s " \
|
||||
" WHERE id = %s",
|
||||
(base64.encodestring(out), len(out), par.file_id))
|
||||
cr.commit()
|
||||
except Exception, e:
|
||||
logging.getLogger('document.storage').exception('Cannot update db file #%d for close:', par.file_id)
|
||||
raise
|
||||
finally:
|
||||
cr.close()
|
||||
StringIO.close(self)
|
||||
|
||||
class document_storage(osv.osv):
|
||||
""" The primary object for data storage.
|
||||
@@ -110,7 +236,7 @@ class document_storage(osv.osv):
|
||||
'group_ids': fields.many2many('res.groups', 'document_storage_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),
|
||||
('realstore','External file 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"),
|
||||
@@ -147,6 +273,51 @@ class document_storage(osv.osv):
|
||||
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_file(self, cr, uid, id, file_node, mode, context=None):
|
||||
""" Return a file-like object for the contents of some node
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
boo = self.browse(cr, uid, id, context)
|
||||
if not boo.online:
|
||||
raise RuntimeError('media offline')
|
||||
|
||||
ira = self.pool.get('ir.attachment').browse(cr, uid, file_node.file_id, context=context)
|
||||
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:
|
||||
self._doclog.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id)
|
||||
raise IOError(errno.ENOENT, 'No file can be located')
|
||||
fpath = os.path.join(boo.path, ira.store_fname)
|
||||
return nodefd_file(file_node, path=fpath, mode=mode)
|
||||
|
||||
elif boo.type == 'db':
|
||||
# TODO: we need a better api for large files
|
||||
return nodefd_db(file_node, ira_browse=ira, mode=mode)
|
||||
|
||||
elif boo.type == 'db64':
|
||||
return nodefd_db64(file_node, ira_browse=ira, mode=mode)
|
||||
|
||||
elif boo.type == 'realstore':
|
||||
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:
|
||||
self._doclog.warning("ir.attachment #%d does not have a filename, trying the name." %ira.id)
|
||||
sfname = ira.name
|
||||
fpath = os.path.join(boo.path,ira.store_fname or ira.name)
|
||||
if not os.path.exists(fpath):
|
||||
raise IOError("File not found: %s" % fpath)
|
||||
return nodefd_file(file_node, path=fpath, mode=mode)
|
||||
|
||||
elif boo.type == 'virtual':
|
||||
raise ValueError('Virtual storage does not support static files')
|
||||
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
|
||||
def __get_data_3(self, cr, uid, boo, ira, context):
|
||||
if not boo.online:
|
||||
raise RuntimeError('media offline')
|
||||
@@ -159,13 +330,21 @@ class document_storage(osv.osv):
|
||||
return None
|
||||
fpath = os.path.join(boo.path, ira.store_fname)
|
||||
return file(fpath, 'rb').read()
|
||||
elif boo.type == 'db':
|
||||
elif boo.type == 'db64':
|
||||
# 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 == 'db':
|
||||
# We do an explicit query, to avoid type transformations.
|
||||
cr.execute('SELECT db_datas FROM ir_attachment WHERE id = %s', (ira.id,))
|
||||
res = cr.fetchone()
|
||||
if res:
|
||||
return res[0]
|
||||
else:
|
||||
return ''
|
||||
elif boo.type == 'realstore':
|
||||
if not ira.store_fname:
|
||||
# On a migrated db, some files may have the wrong storage type
|
||||
@@ -180,6 +359,10 @@ class document_storage(osv.osv):
|
||||
return None
|
||||
else:
|
||||
raise IOError("File not found: %s" % fpath)
|
||||
|
||||
elif boo.type == 'virtual':
|
||||
raise ValueError('Virtual storage does not support static files')
|
||||
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
|
||||
@@ -228,7 +411,13 @@ class document_storage(osv.osv):
|
||||
raise except_orm(_('Error!'), str(e))
|
||||
elif boo.type == 'db':
|
||||
filesize = len(data)
|
||||
# will that work for huge data? TODO
|
||||
# will that work for huge data?
|
||||
out = psycopg2.Binary(data)
|
||||
cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
|
||||
(out, file_node.file_id))
|
||||
elif boo.type == 'db64':
|
||||
filesize = len(data)
|
||||
# will that work for huge data?
|
||||
out = base64.encodestring(data)
|
||||
cr.execute('UPDATE ir_attachment SET db_datas = %s WHERE id = %s',
|
||||
(out, file_node.file_id))
|
||||
@@ -260,6 +449,10 @@ class document_storage(osv.osv):
|
||||
except Exception,e :
|
||||
self._doclog.warning("Couldn't save data:", exc_info=True)
|
||||
raise except_orm(_('Error!'), str(e))
|
||||
|
||||
elif boo.type == 'virtual':
|
||||
raise ValueError('Virtual storage does not support static files')
|
||||
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
|
||||
@@ -276,16 +469,21 @@ class document_storage(osv.osv):
|
||||
self._doclog.debug('Cannot index file:', exc_info=True)
|
||||
pass
|
||||
|
||||
try:
|
||||
icont_u = ustr(icont)
|
||||
except UnicodeError:
|
||||
icont_u = ''
|
||||
|
||||
# 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))
|
||||
(store_fname, filesize, icont_u, mime, file_node.file_id))
|
||||
file_node.content_length = filesize
|
||||
file_node.content_type = mime
|
||||
return True
|
||||
except Exception, e :
|
||||
self._doclog.warning( "Couldn't save data:", exc_info=True)
|
||||
self._doclog.warning("Couldn't save data:", exc_info=True)
|
||||
# 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))
|
||||
@@ -303,7 +501,7 @@ class document_storage(osv.osv):
|
||||
return None
|
||||
path = storage_bo.path
|
||||
return (storage_bo.id, 'file', os.path.join(path, fname))
|
||||
elif storage_bo.type == 'db':
|
||||
elif storage_bo.type in ('db', 'db64'):
|
||||
return None
|
||||
elif storage_bo.type == 'realstore':
|
||||
fname = fil_bo.store_fname
|
||||
@@ -312,7 +510,7 @@ class document_storage(osv.osv):
|
||||
path = storage_bo.path
|
||||
return ( storage_bo.id, 'file', os.path.join(path, fname))
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
raise TypeError("No %s storage" % storage_bo.type)
|
||||
|
||||
def do_unlink(self, cr, uid, unres):
|
||||
for id, ktype, fname in unres:
|
||||
@@ -326,6 +524,92 @@ class document_storage(osv.osv):
|
||||
|
||||
return True
|
||||
|
||||
def simple_rename(self, cr, uid, file_node, new_name, context=None):
|
||||
""" A preparation for a file rename.
|
||||
It will not affect the database, but merely check and perhaps
|
||||
rename the realstore file.
|
||||
|
||||
@return the dict of values that can safely be be stored in the db.
|
||||
"""
|
||||
sbro = self.browse(cr, uid, file_node.storage_id, context=context)
|
||||
assert sbro, "The file #%d didn't provide storage" % file_node.file_id
|
||||
|
||||
if sbro.type in ('filestore', 'db', 'db64'):
|
||||
# nothing to do for a rename, allow to change the db field
|
||||
return { 'name': new_name, 'datas_fname': new_name }
|
||||
elif sbro.type == 'realstore':
|
||||
fname = fil_bo.store_fname
|
||||
if not fname:
|
||||
return ValueError("Tried to rename a non-stored file")
|
||||
path = storage_bo.path
|
||||
oldpath = os.path.join(path, fname)
|
||||
|
||||
for ch in ('*', '|', "\\", '/', ':', '"', '<', '>', '?', '..'):
|
||||
if ch in new_name:
|
||||
raise ValueError("Invalid char %s in name %s" %(ch, new_name))
|
||||
|
||||
file_node.fix_ppath(cr, ira)
|
||||
npath = file_node.full_path() or []
|
||||
dpath = [path,]
|
||||
dpath.extend(npath[:-1])
|
||||
dpath.append(new_name)
|
||||
newpath = os.path.join(*dpath)
|
||||
# print "old, new paths:", oldpath, newpath
|
||||
os.rename(oldpath, newpath)
|
||||
return { 'name': new_name, 'datas_fname': new_name, 'store_fname': new_name }
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
|
||||
def simple_move(self, cr, uid, file_node, ndir_bro, context=None):
|
||||
""" A preparation for a file move.
|
||||
It will not affect the database, but merely check and perhaps
|
||||
move the realstore file.
|
||||
|
||||
@param ndir_bro a browse object of document.directory, where this
|
||||
file should move to.
|
||||
@return the dict of values that can safely be be stored in the db.
|
||||
"""
|
||||
sbro = self.browse(cr, uid, file_node.storage_id, context=context)
|
||||
assert sbro, "The file #%d didn't provide storage" % file_node.file_id
|
||||
|
||||
par = ndir_bro
|
||||
psto = None
|
||||
while par:
|
||||
if par.storage_id:
|
||||
psto = par.storage_id.id
|
||||
break
|
||||
par = par.parent_id
|
||||
if file_node.storage_id != psto:
|
||||
self._doclog.debug('Cannot move file %r from %r to %r', file_node, file_node.parent, ndir_bro.name)
|
||||
raise NotImplementedError('Cannot move files between storage media')
|
||||
|
||||
if sbro.type in ('filestore', 'db', 'db64'):
|
||||
# nothing to do for a rename, allow to change the db field
|
||||
return { 'parent_id': ndir_bro.id }
|
||||
elif sbro.type == 'realstore':
|
||||
raise NotImplementedError("Cannot move in realstore, yet") # TODO
|
||||
fname = fil_bo.store_fname
|
||||
if not fname:
|
||||
return ValueError("Tried to rename a non-stored file")
|
||||
path = storage_bo.path
|
||||
oldpath = os.path.join(path, fname)
|
||||
|
||||
for ch in ('*', '|', "\\", '/', ':', '"', '<', '>', '?', '..'):
|
||||
if ch in new_name:
|
||||
raise ValueError("Invalid char %s in name %s" %(ch, new_name))
|
||||
|
||||
file_node.fix_ppath(cr, ira)
|
||||
npath = file_node.full_path() or []
|
||||
dpath = [path,]
|
||||
dpath.extend(npath[:-1])
|
||||
dpath.append(new_name)
|
||||
newpath = os.path.join(*dpath)
|
||||
# print "old, new paths:", oldpath, newpath
|
||||
os.rename(oldpath, newpath)
|
||||
return { 'name': new_name, 'datas_fname': new_name, 'store_fname': new_name }
|
||||
else:
|
||||
raise TypeError("No %s storage" % boo.type)
|
||||
|
||||
|
||||
document_storage()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<field name="online"/>
|
||||
<field name="readonly"/>
|
||||
</group>
|
||||
<group colspan="2" attrs="{'invisible':[('type','=','db')]}">
|
||||
<group colspan="2" attrs="{'invisible':[('type','in',['db', 'db64'])]}">
|
||||
<field name="path"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -61,6 +61,7 @@
|
||||
name="Storage Media"
|
||||
action="action_document_storage_form"
|
||||
id="menu_document_storage_media"
|
||||
groups="base.group_extended"
|
||||
parent="menu_document_management_configuration"/>
|
||||
|
||||
<record model="ir.ui.view" id="view_document_directory_form">
|
||||
@@ -68,25 +69,32 @@
|
||||
<field name="model">document.directory</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Directories">
|
||||
<form string="Directories" col="6">
|
||||
<field name="name" select="1" colspan="4"/>
|
||||
<field name="user_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="storage_id" />
|
||||
<field name="storage_id" widget="selection" />
|
||||
<field name="user_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Definition">
|
||||
<separator string="Directory Type" colspan="4"/>
|
||||
<field name="type"/>
|
||||
<field name="ressource_type_id" on_change="onchange_content_id(ressource_type_id)" attrs="{'required': [('type','=','ressource')], 'readonly': [('type','=','static')]}"/>
|
||||
<newline/>
|
||||
<field name="domain" attrs="{'required': [('type','=','ressource')], 'readonly': [('type','=','static')]}"/>
|
||||
<field name="ressource_tree"/>
|
||||
<field name="resource_field" domain="[('model_id','=',ressource_type_id), ('ttype', 'in', ('char', 'selection', 'date', 'datetime'))]"/>
|
||||
<field name="ressource_id" select="2" readonly="1"/>
|
||||
<field name="ressource_parent_type_id"/>
|
||||
<group colspan="4" col="4" attrs="{'invisible': [('type','!=','ressource')]}">
|
||||
<field name="ressource_type_id" on_change="onchange_content_id(ressource_type_id)"
|
||||
attrs="{'required': [('type','=','ressource')] }"/>
|
||||
<newline/>
|
||||
<field name="resource_field" domain="[('model_id','=',ressource_type_id), ('ttype', 'in', ('char', 'selection', 'date', 'datetime'))]"/>
|
||||
<field name="ressource_tree"/>
|
||||
<newline/>
|
||||
<field name="domain" attrs="{'required': [('type','=','ressource')], 'readonly': [('type','=','static')]}"/>
|
||||
</group>
|
||||
<group colspan="4" col="4">
|
||||
<field name="ressource_parent_type_id"/>
|
||||
<field name="ressource_id" select="2" readonly="1"/>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
<page string="Generated Files">
|
||||
<page string="Generated Files" groups="base.group_extended">
|
||||
<label colspan="4" string="For each entry here, virtual files will appear in this folder." />
|
||||
<field name="content_ids" nolabel="1" colspan="4" attrs="{'readonly': [('ressource_type_id','=',False)]}">
|
||||
<form string="Contents">
|
||||
@@ -108,6 +116,7 @@
|
||||
</field>
|
||||
</page>
|
||||
<page string="Dynamic context" groups="base.group_extended">
|
||||
<label colspan="4" string="Define words in the context, for all child directories and files" />
|
||||
<field name="dctx_ids" nolabel="1" colspan="4">
|
||||
<tree string="Fields" editable="bottom">
|
||||
<field name="field"/>
|
||||
@@ -120,6 +129,8 @@
|
||||
</field>
|
||||
</page>
|
||||
<page string="Security">
|
||||
<label colspan="4" string="Only members of these groups will have access to this directory and its files." />
|
||||
<label colspan="4" string="These groups, however, do NOT apply to children directories, which must define their own groups." />
|
||||
<field name="group_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -213,44 +224,46 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Documents">
|
||||
<group colspan="4" col="6">
|
||||
<field name="name" select="1" />
|
||||
<field name="type"/>
|
||||
<field name="name" select="1" colspan="4" />
|
||||
<field name="parent_id"/>
|
||||
<newline/>
|
||||
<field name="user_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Attachment">
|
||||
<group col="2" colspan="2">
|
||||
<separator string="Data" colspan="2"/>
|
||||
<field name="type"/>
|
||||
<newline />
|
||||
<group col="2" colspan="2" attrs="{'invisible':[('type','=','url')]}">
|
||||
<field name="datas" filename="datas_fname"/>
|
||||
<field name="datas_fname" select="1"/>
|
||||
<field name="datas" filename="datas_fname"/>
|
||||
<field name="datas_fname" select="1"/>
|
||||
</group>
|
||||
<group col="2" colspan="2" attrs="{'invisible':[('type','=','binary')]}">
|
||||
<field name="url" widget="url"/>
|
||||
<field name="url" widget="url"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2" colspan="2">
|
||||
<separator string="Relation" colspan="2"/>
|
||||
<field name="res_name" readonly="1"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id"/>
|
||||
</group>
|
||||
<group col="4" colspan="4">
|
||||
<separator string="History" colspan="4"/>
|
||||
<group col="4" colspan="4">
|
||||
<field name="create_uid"/>
|
||||
<field name="create_date"/>
|
||||
</group>
|
||||
<group col="4" colspan="4">
|
||||
<field name="write_uid"/>
|
||||
<field name="write_date"/>
|
||||
<field name="res_id" invisible="True"/>
|
||||
<group col="2" colspan="2" attrs="{'invisible': [('res_id','=',0)]}">
|
||||
<separator string="Attached To" colspan="2"/>
|
||||
<field name="res_model" readonly="True" invisible="True"/>
|
||||
<field name="res_name" readonly="1"/>
|
||||
</group>
|
||||
<separator string="Related to" colspan="2"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
<group col="2" colspan="2" groups="base.group_extended">
|
||||
<separator string="Created" colspan="2"/>
|
||||
<field name="create_uid" readonly="1"/>
|
||||
<field name="create_date" readonly="1"/>
|
||||
</group>
|
||||
<group col="2" colspan="2" groups="base.group_extended">
|
||||
<separator string="Modified" colspan="2"/>
|
||||
<field name="write_uid" readonly="1"/>
|
||||
<field name="write_date" readonly="1"/>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
<page string="Security">
|
||||
<field name="group_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Indexed Content" groups="base.group_extended">
|
||||
<field name="index_content" colspan="4" nolabel="1"/>
|
||||
@@ -269,11 +282,13 @@
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="IR Attachment">
|
||||
<filter icon="terp-go-month" string="Recent"
|
||||
help="less 1 month modified/created attachments"
|
||||
<filter icon="terp-go-month" string="Recent Month"
|
||||
help="Attachment modified/created last 30 days"
|
||||
domain="[('create_date','<=', time.strftime('%%Y-%%m-%%d')),('create_date','>',(datetime.date.today()-datetime.timedelta(days=30)).strftime('%%Y-%%m-%%d'))]"
|
||||
/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name"/>
|
||||
<field name="parent_id" />
|
||||
<field name="user_id">
|
||||
<filter icon="terp-personal"
|
||||
domain="[('user_id','=', False)]"
|
||||
@@ -284,9 +299,9 @@
|
||||
<newline/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Partner" icon="terp-personal" domain="[]"
|
||||
context="{'group_by':'partner_id'}" />
|
||||
context="{'group_by':'partner_id'}" groups="base.group_extended"/>
|
||||
<filter string="Directory" icon="terp-folder-green" domain="[]" context="{'group_by':'parent_id'}"/>
|
||||
<filter string="Type" domain="[]" context="{'group_by':'type'}"/>
|
||||
<filter string="Type" domain="[]" context="{'group_by':'type'}" groups="base.group_extended"/>
|
||||
<filter string="Owner" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Company" icon="terp-personal" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
@@ -301,11 +316,12 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:type in ('url',)">
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="datas_fname"/>
|
||||
<field name="parent_id" />
|
||||
<field name="user_id"/>
|
||||
<field name="create_date"/>
|
||||
<field name="write_date"/>
|
||||
<field name="partner_id" groups="base.group_extended" />
|
||||
<field name="type" groups="base.group_extended"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
|
||||
+457
-169
File diff suppressed because it is too large
Load Diff
@@ -33,22 +33,22 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_report_document_user_search" model="ir.ui.view">
|
||||
<record id="view_report_document_user_search" model="ir.ui.view">
|
||||
<field name="name">report.document.user.search</field>
|
||||
<field name="model">report.document.user</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="All users files">
|
||||
<group col="12" colspan="4">
|
||||
<group col="12" colspan="4">
|
||||
<separator orientation="vertical"/>
|
||||
<filter icon="terp-go-year" string="This Year" domain="[('name','=',time.localtime()[0])]" help="All Months Files"/>
|
||||
<filter icon="terp-go-month" string="This Month" domain="[('month','=',time.localtime()[1])]" help="This Months Files"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="month" select="1"/>
|
||||
<field name="user" select="1"/>
|
||||
<field name="directory" select="1"/>
|
||||
</group>
|
||||
<filter icon="terp-go-year" string="This Year" domain="[('name','=',time.localtime()[0])]" help="All Months Files"/>
|
||||
<filter icon="terp-go-month" string="This Month" domain="[('month','=',time.localtime()[1])]" help="This Months Files"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="month" select="1"/>
|
||||
<field name="user" select="1"/>
|
||||
<field name="directory" select="1"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
@@ -61,7 +61,7 @@
|
||||
<field name="res_model">report.document.user</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="context">{"search_default_user":uid}</field>
|
||||
<field name="context">{'search_default_user': 'user_id'}</field>
|
||||
<field name="search_view_id" ref="view_report_document_user_search"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
-
|
||||
In order to test the document management
|
||||
-
|
||||
I make sure the default installation has some storage and root directory.
|
||||
-
|
||||
!assert {model: document.storage, id: storage_default }:
|
||||
- id != False
|
||||
-
|
||||
!assert {model: document.directory, id: dir_root}:
|
||||
- storage_id != False
|
||||
-
|
||||
I create a "Testing" folder where all the test data will go.
|
||||
-
|
||||
!record {model: document.directory, id: dir_tests }:
|
||||
name: 'Testing'
|
||||
parent_id: dir_root
|
||||
-
|
||||
I create an attachment into the root folder (w. empty fields, test that
|
||||
defaults work)
|
||||
-
|
||||
!record {model: ir.attachment, id: file_test1 }:
|
||||
name: Test file
|
||||
-
|
||||
I delete the attachment from the root folder
|
||||
-
|
||||
!delete {model: ir.attachment, id: file_test1, search: }
|
||||
-
|
||||
I create an attachment into the Testing folder.
|
||||
-
|
||||
!record {model: ir.attachment, id: file_test2 }:
|
||||
name: Test file 2
|
||||
parent_id: dir_tests
|
||||
-
|
||||
I update the attachment with data, namely "abcd"
|
||||
-
|
||||
!record {model: ir.attachment, id: file_test2 }:
|
||||
datas: "abcd"
|
||||
-
|
||||
I test that the datas of the attachment are correct
|
||||
-
|
||||
!assert {model: ir.attachment, id: file_test2 }:
|
||||
- datas == "abcd\n"
|
||||
- file_size == 5
|
||||
- file_type == 'text/plain'
|
||||
-
|
||||
I rename the attachment.
|
||||
-
|
||||
!record {model: ir.attachment, id: file_test2 }:
|
||||
name: Test renamed 2
|
||||
-
|
||||
I search the testing folder for attachments.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
ids = self.search(cr, uid, [('parent_id.name','=', 'Testing'), ('name','=','Test renamed 2')])
|
||||
assert ids == [ ref("file_test2") ], ids
|
||||
-
|
||||
I create an attachment to a 3rd resource, eg. a res.country
|
||||
-
|
||||
!record {model: ir.attachment, id: attach_3rd }:
|
||||
name: 'Res country attachment.txt'
|
||||
parent_id: dir_tests
|
||||
datas: 'defg'
|
||||
res_model: res.country
|
||||
res_id: !eval ref("base.za")
|
||||
-
|
||||
I search for the res.country attachment
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
ids = self.search(cr, uid, [('res_model', '=', 'res.country'), ('res_id', '=', ref("base.za"))])
|
||||
assert ids == [ ref("attach_3rd")], ids
|
||||
-
|
||||
!delete {model: ir.attachment, id: attach_3rd, search: }
|
||||
-
|
||||
@@ -26,20 +26,23 @@
|
||||
'category': 'Generic Modules/Others',
|
||||
'description': """This is a support FTP Interface with document management system.
|
||||
With this module you would not only be able to access documents through open erp
|
||||
but you would also be able to connect with them through the file system using the FTP protocol.
|
||||
but you would also be able to connect with them through the file system using the
|
||||
a FTP client.
|
||||
""",
|
||||
'author': 'Tiny',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base', 'document'],
|
||||
'init_xml': [],
|
||||
'update_xml': [
|
||||
'wizard/ftp_configuration_view.xml',
|
||||
'wizard/ftp_browse_view.xml',
|
||||
'wizard/ftp_configuration_view.xml',
|
||||
'wizard/ftp_browse_view.xml',
|
||||
'security/ir.model.access.csv'
|
||||
],
|
||||
'demo_xml': [],
|
||||
'test': [
|
||||
'test/document_ftp_test.yml',
|
||||
'test/document_ftp_test2.yml',
|
||||
# 'test/document_ftp_test2.yml',
|
||||
'test/document_ftp_test4.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -221,6 +221,33 @@ def _strerror(err):
|
||||
else:
|
||||
return err.strerror
|
||||
|
||||
def _to_unicode(s):
|
||||
try:
|
||||
return s.decode('utf-8')
|
||||
except UnicodeError:
|
||||
pass
|
||||
try:
|
||||
return s.decode('latin')
|
||||
except UnicodeError:
|
||||
pass
|
||||
try:
|
||||
return s.encode('ascii')
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
def _to_decode(s):
|
||||
try:
|
||||
return s.encode('utf-8')
|
||||
except UnicodeError:
|
||||
pass
|
||||
try:
|
||||
return s.encode('latin')
|
||||
except UnicodeError:
|
||||
pass
|
||||
try:
|
||||
return s.decode('ascii')
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
# --- library defined exceptions
|
||||
|
||||
@@ -793,7 +820,7 @@ class DTPHandler(asyncore.dispatcher):
|
||||
if self.transfer_finished:
|
||||
self.cmd_channel.respond("226 Transfer complete.")
|
||||
if self.file_obj:
|
||||
fname = self.cmd_channel.fs.fs2ftp(self.file_obj.name)
|
||||
fname = self.file_obj.name
|
||||
self.cmd_channel.log('"%s" %s.' %(fname, action))
|
||||
else:
|
||||
tot_bytes = self.get_transmitted_bytes()
|
||||
@@ -1138,19 +1165,11 @@ class AbstractedFS:
|
||||
|
||||
# note: the following operations are no more blocking
|
||||
|
||||
def get_list_dir(self, path):
|
||||
def get_list_dir(self, datacr):
|
||||
""""Return an iterator object that yields a directory listing
|
||||
in a form suitable for LIST command.
|
||||
"""
|
||||
if self.isdir(path):
|
||||
listing = self.listdir(path)
|
||||
listing.sort()
|
||||
return self.format_list(path, listing)
|
||||
# if path is a file or a symlink we return information about it
|
||||
else:
|
||||
basedir, filename = os.path.split(path)
|
||||
self.lstat(path) # raise exc in case of problems
|
||||
return self.format_list(basedir, [filename])
|
||||
raise DeprecationWarning()
|
||||
|
||||
def get_stat_dir(self, rawline):
|
||||
"""Return an iterator object that yields a list of files
|
||||
@@ -1591,26 +1610,8 @@ class FTPHandler(asynchat.async_chat):
|
||||
|
||||
def __check_path(self, cmd, line):
|
||||
"""Check whether a path is valid."""
|
||||
# For the following commands we have to make sure that the real
|
||||
# path destination belongs to the user's root directory.
|
||||
# If provided path is a symlink we follow its final destination
|
||||
# to do so.
|
||||
if cmd in ('APPE','CWD','DELE','MDTM','NLST','MLSD','MLST','RETR',
|
||||
'RMD','SIZE','STOR','XCWD','XRMD'):
|
||||
datacr = None
|
||||
datacr = self.fs.get_cr(line)
|
||||
try:
|
||||
if not self.fs.validpath(self.fs.ftp2fs(line, datacr)):
|
||||
line = self.fs.ftpnorm(line)
|
||||
err = '"%s" points to a path which is outside ' \
|
||||
"the user's root directory" %line
|
||||
self.respond("550 %s." %err)
|
||||
self.log('FAIL %s "%s". %s.' %(cmd, line, err))
|
||||
self.fs.close_cr(datacr)
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
self.fs.close_cr(datacr)
|
||||
|
||||
# Always true, we will only check later, once we have a cursor
|
||||
return True
|
||||
|
||||
def __check_perm(self, cmd, line, datacr):
|
||||
@@ -1623,6 +1624,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
'RNFR':'f',
|
||||
'MKD':'m', 'XMKD':'m',
|
||||
'STOR':'w'}
|
||||
raise NotImplementedError
|
||||
if cmd in map:
|
||||
if cmd == 'STAT' and not line:
|
||||
return True
|
||||
@@ -2032,56 +2034,71 @@ class FTPHandler(asynchat.async_chat):
|
||||
# - Some older FTP clients erroneously issue /bin/ls-like LIST
|
||||
# formats in which case we fall back on cwd as default.
|
||||
if not line or line.lower() in ('-a', '-l', '-al', '-la'):
|
||||
line = self.fs.cwd
|
||||
line = ''
|
||||
datacr = None
|
||||
try:
|
||||
data = None
|
||||
data = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, data)
|
||||
line = self.fs.ftpnorm(line)
|
||||
iterator = self.run_as_current_user(self.fs.get_list_dir, path)
|
||||
datacr = self.fs.get_crdata(line, mode='list')
|
||||
iterator = self.run_as_current_user(self.fs.get_list_dir, datacr)
|
||||
except IOError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
self.respond('550 %s.'% err.strerror)
|
||||
return
|
||||
except OSError, err:
|
||||
self.fs.close_cr(data)
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
self.log('FAIL LIST "%s". %s.' %(line, why))
|
||||
self.respond('550 %s.' %why)
|
||||
else:
|
||||
self.fs.close_cr(data)
|
||||
self.log('OK LIST "%s". Transfer starting.' %line)
|
||||
return
|
||||
try:
|
||||
self.log('OK LIST "%s". Transfer starting.' % line)
|
||||
producer = BufferedIteratorProducer(iterator)
|
||||
self.push_dtp_data(producer, isproducer=True)
|
||||
finally:
|
||||
self.fs.close_cr(datacr)
|
||||
|
||||
|
||||
def ftp_NLST(self, line):
|
||||
"""Return a list of files in the specified directory in a
|
||||
compact form to the client.
|
||||
"""
|
||||
if not line:
|
||||
line = self.fs.cwd
|
||||
line = ''
|
||||
|
||||
datacr = None
|
||||
try:
|
||||
data = None
|
||||
data = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, data)
|
||||
line = self.fs.ftpnorm(line)
|
||||
if self.fs.isdir(path):
|
||||
listing = self.run_as_current_user(self.fs.listdir, path)
|
||||
listing = map(lambda x:os.path.split(x.path)[1], listing)
|
||||
datacr = self.fs.get_crdata(line, mode='list')
|
||||
if not datacr:
|
||||
datacr = ( None, None, None )
|
||||
if self.fs.isdir(datacr[1]):
|
||||
nodelist = self.run_as_current_user(self.fs.listdir, datacr)
|
||||
else:
|
||||
# if path is a file we just list its name
|
||||
self.fs.lstat(path) # raise exc in case of problems
|
||||
basedir, filename = os.path.split(line)
|
||||
listing = [filename]
|
||||
nodelist = [datacr[1],]
|
||||
|
||||
listing = []
|
||||
for nl in nodelist:
|
||||
if isinstance(nl.path, (list, tuple)):
|
||||
listing.append(nl.path[-1])
|
||||
else:
|
||||
listing.append(nl.path) # assume string
|
||||
except IOError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
self.respond('550 %s.'% err.strerror)
|
||||
return
|
||||
except OSError, err:
|
||||
self.fs.close_cr(data)
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
self.log('FAIL NLST "%s". %s.' %(line, why))
|
||||
self.respond('550 %s.' %why)
|
||||
else:
|
||||
self.fs.close_cr(data)
|
||||
data = ''
|
||||
if listing:
|
||||
listing.sort()
|
||||
data = '\r\n'.join(listing) + '\r\n'
|
||||
self.log('OK NLST "%s". Transfer starting.' %line)
|
||||
self.push_dtp_data(data)
|
||||
return
|
||||
|
||||
self.fs.close_cr(datacr)
|
||||
data = ''
|
||||
if listing:
|
||||
listing.sort()
|
||||
data = ''.join([ _to_decode(x) + '\r\n' for x in listing ])
|
||||
self.log('OK NLST "%s". Transfer starting.' %line)
|
||||
self.push_dtp_data(data)
|
||||
|
||||
# --- MLST and MLSD commands
|
||||
|
||||
@@ -2096,18 +2113,15 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""
|
||||
# if no argument, fall back on cwd as default
|
||||
if not line:
|
||||
line = self.fs.cwd
|
||||
line = ''
|
||||
datacr = None
|
||||
try:
|
||||
datacr = None
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
basedir, basename = os.path.split(path)
|
||||
datacr = self.fs.get_crdata(line, mode='list')
|
||||
perms = self.authorizer.get_perms(self.username)
|
||||
iterator = self.run_as_current_user(self.fs.format_mlsx, basedir,
|
||||
[basename], perms, self.current_facts, ignore_err=False)
|
||||
iterator = self.run_as_current_user(self.fs.format_mlsx, datacr[0], datacr[1].parent,
|
||||
[datacr[1],], perms, self.current_facts, ignore_err=False)
|
||||
data = ''.join(iterator)
|
||||
except OSError, err:
|
||||
except EnvironmentError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
self.log('FAIL MLST "%s". %s.' %(line, why))
|
||||
@@ -2129,19 +2143,18 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""
|
||||
# if no argument, fall back on cwd as default
|
||||
if not line:
|
||||
line = self.fs.cwd
|
||||
line = ''
|
||||
|
||||
datacr = None
|
||||
try:
|
||||
datacr = None
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
datacr = self.fs.get_crdata(line, mode='list')
|
||||
# RFC-3659 requires 501 response code if path is not a directory
|
||||
if not self.fs.isdir(path):
|
||||
if not self.fs.isdir(datacr[1]):
|
||||
err = 'No such directory'
|
||||
self.log('FAIL MLSD "%s". %s.' %(line, err))
|
||||
self.respond("501 %s." %err)
|
||||
return
|
||||
listing = self.run_as_current_user(self.fs.listdir, path)
|
||||
listing = self.run_as_current_user(self.fs.listdir, datacr)
|
||||
except OSError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
@@ -2150,7 +2163,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
else:
|
||||
self.fs.close_cr(datacr)
|
||||
perms = self.authorizer.get_perms(self.username)
|
||||
iterator = self.fs.format_mlsx(path, listing, perms,
|
||||
iterator = self.fs.format_mlsx(datacr[0], datacr[1], listing, perms,
|
||||
self.current_facts)
|
||||
producer = BufferedIteratorProducer(iterator)
|
||||
self.log('OK MLSD "%s". Transfer starting.' %line)
|
||||
@@ -2160,12 +2173,10 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""Retrieve the specified file (transfer from the server to the
|
||||
client)
|
||||
"""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = None
|
||||
datacr = self.fs.get_cr(line)
|
||||
file = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
fd = self.run_as_current_user(self.fs.open, file, 'rb')
|
||||
datacr = self.fs.get_crdata(line, mode='file')
|
||||
fd = self.run_as_current_user(self.fs.open, datacr, 'rb')
|
||||
except OSError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
@@ -2174,7 +2185,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
return
|
||||
except IOError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
why = err.strerror
|
||||
self.log('FAIL RETR "%s". %s.' %(line, why))
|
||||
self.respond('550 %s.' %why)
|
||||
return
|
||||
@@ -2187,7 +2198,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
# the REST.
|
||||
ok = 0
|
||||
try:
|
||||
assert not self.restart_position > self.fs.getsize(file)
|
||||
assert not self.restart_position > self.fs.getsize(datacr)
|
||||
fd.seek(self.restart_position)
|
||||
ok = 1
|
||||
except AssertionError:
|
||||
@@ -2217,14 +2228,9 @@ class FTPHandler(asynchat.async_chat):
|
||||
else:
|
||||
cmd = 'STOR'
|
||||
|
||||
line = self.fs.ftpnorm(line)
|
||||
basedir,basename = os.path.split(line)
|
||||
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
file = self.fs.ftp2fs(basedir, datacr)
|
||||
|
||||
datacr = self.fs.get_crdata(line,mode='create')
|
||||
except OSError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
@@ -2235,7 +2241,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
if self.restart_position:
|
||||
mode = 'r+'
|
||||
try:
|
||||
fd = self.run_as_current_user(self.fs.create, file, basename, mode + 'b')
|
||||
fd = self.run_as_current_user(self.fs.create, datacr, datacr[2], mode + 'b')
|
||||
except IOError, err:
|
||||
self.fs.close_cr(datacr)
|
||||
why = _strerror(err)
|
||||
@@ -2251,7 +2257,7 @@ class FTPHandler(asynchat.async_chat):
|
||||
# specified in the REST.
|
||||
ok = 0
|
||||
try:
|
||||
assert not self.restart_position > self.fs.getsize(self.fs.ftp2fs(line, datacr))
|
||||
assert not self.restart_position > self.fs.getsize(datacr)
|
||||
fd.seek(self.restart_position)
|
||||
ok = 1
|
||||
except AssertionError:
|
||||
@@ -2293,15 +2299,12 @@ class FTPHandler(asynchat.async_chat):
|
||||
self.respond("450 Can't STOU while REST request is pending.")
|
||||
return
|
||||
|
||||
datacr = None
|
||||
datacr = self.fs.get_cr(line)
|
||||
|
||||
if line:
|
||||
line = self.fs.ftpnorm(line)
|
||||
basedir,prefix = os.path.split(line)
|
||||
basedir = self.fs.ftp2fs(basedir, datacr)
|
||||
#prefix = prefix + '.'
|
||||
datacr = self.fs.get_crdata(line, mode='create')
|
||||
# TODO
|
||||
else:
|
||||
# TODO
|
||||
basedir = self.fs.ftp2fs(self.fs.cwd, datacr)
|
||||
prefix = 'ftpd.'
|
||||
try:
|
||||
@@ -2500,21 +2503,23 @@ class FTPHandler(asynchat.async_chat):
|
||||
|
||||
def ftp_PWD(self, line):
|
||||
"""Return the name of the current working directory to the client."""
|
||||
self.respond('257 "%s" is the current directory.' %self.fs.cwd)
|
||||
cwd = self.fs.get_cwd()
|
||||
self.respond('257 "%s" is the current directory.' % cwd)
|
||||
|
||||
def ftp_CWD(self, line):
|
||||
"""Change the current working directory."""
|
||||
# TODO: a lot of FTP servers go back to root directory if no
|
||||
# check: a lot of FTP servers go back to root directory if no
|
||||
# arg is provided but this is not specified in RFC-959.
|
||||
# Search for official references about this behaviour.
|
||||
if not line:
|
||||
line = '/'
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
self.run_as_current_user(self.fs.chdir, path)
|
||||
except OSError, err:
|
||||
datacr = self.fs.get_crdata(line,'cwd')
|
||||
self.run_as_current_user(self.fs.chdir, datacr)
|
||||
cwd = self.fs.get_cwd()
|
||||
self.log('OK CWD "%s".' % cwd)
|
||||
self.respond('250 "%s" is the current directory.' % cwd)
|
||||
except EnvironmentError, err:
|
||||
self.log("Could not cwd: %s" % err)
|
||||
if err.errno==2:
|
||||
why = 'Authentication Required or Failed'
|
||||
self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why))
|
||||
@@ -2523,9 +2528,6 @@ class FTPHandler(asynchat.async_chat):
|
||||
why = _strerror(err)
|
||||
self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why))
|
||||
self.respond('550 %s.' %why)
|
||||
else:
|
||||
self.log('OK CWD "%s".' %self.fs.cwd)
|
||||
self.respond('250 "%s" is the current directory.' %self.fs.cwd)
|
||||
self.fs.close_cr(datacr)
|
||||
|
||||
def ftp_CDUP(self, line):
|
||||
@@ -2554,17 +2556,15 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
if self.fs.isdir(path):
|
||||
why = "%s is not retrievable" %line
|
||||
self.log('FAIL SIZE "%s". %s.' %(line, why))
|
||||
self.respond("550 %s." %why)
|
||||
self.fs.close_cr(datacr)
|
||||
return
|
||||
size = self.run_as_current_user(self.fs.getsize, path)
|
||||
except OSError, err:
|
||||
datacr = self.fs.get_crdata(line, mode='file')
|
||||
#if self.fs.isdir(datacr[1]):
|
||||
# why = "%s is not retrievable" %line
|
||||
# self.log('FAIL SIZE "%s". %s.' %(line, why))
|
||||
# self.respond("550 %s." %why)
|
||||
# self.fs.close_cr(datacr)
|
||||
# return
|
||||
size = self.run_as_current_user(self.fs.getsize, datacr)
|
||||
except EnvironmentError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL SIZE "%s". %s.' %(line, why))
|
||||
self.respond('550 %s.' %why)
|
||||
@@ -2579,35 +2579,31 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
if not self.fs.isfile(self.fs.realpath(path)):
|
||||
datacr = self.fs.get_crdata(line)
|
||||
if not self.fs.isfile(datacr):
|
||||
why = "%s is not retrievable" %line
|
||||
self.log('FAIL MDTM "%s". %s.' %(line, why))
|
||||
self.respond("550 %s." %why)
|
||||
self.fs.close_cr(datacr)
|
||||
return
|
||||
lmt = self.run_as_current_user(self.fs.getmtime, path)
|
||||
lmt = self.run_as_current_user(self.fs.getmtime, datacr)
|
||||
lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt))
|
||||
self.respond("213 %s" %lmt)
|
||||
self.log('OK MDTM "%s".' %line)
|
||||
except OSError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL MDTM "%s". %s.' %(line, why))
|
||||
self.respond('550 %s.' %why)
|
||||
else:
|
||||
lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt))
|
||||
self.respond("213 %s" %lmt)
|
||||
self.log('OK MDTM "%s".' %line)
|
||||
self.fs.close_cr(datacr)
|
||||
|
||||
def ftp_MKD(self, line):
|
||||
"""Create the specified directory."""
|
||||
datacr = None
|
||||
line = self.fs.ftpnorm(line)
|
||||
basedir,basename = os.path.split(line)
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(basedir, datacr)
|
||||
self.run_as_current_user(self.fs.mkdir, path, basename)
|
||||
datacr = self.fs.get_crdata(line, mode='create')
|
||||
self.run_as_current_user(self.fs.mkdir, datacr, datacr[2])
|
||||
except IOError, err:
|
||||
self.log('FAIL MKD "%s". %s.' %(line, err.strerror))
|
||||
self.respond('550 %s.' % err.strerror)
|
||||
except OSError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL MKD "%s". %s.' %(line, why))
|
||||
@@ -2621,16 +2617,14 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""Remove the specified directory."""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
if self.fs.realpath(path) == self.fs.realpath(self.fs.root):
|
||||
datacr = self.fs.get_crdata(line, mode='delete')
|
||||
if not datacr[1]:
|
||||
msg = "Can't remove root directory."
|
||||
self.respond("550 %s" %msg)
|
||||
self.log('FAIL MKD "/". %s' %msg)
|
||||
self.fs.close_cr(datacr)
|
||||
return
|
||||
self.run_as_current_user(self.fs.rmdir, path)
|
||||
self.run_as_current_user(self.fs.rmdir, datacr)
|
||||
except OSError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL RMD "%s". %s.' %(line, why))
|
||||
@@ -2644,10 +2638,8 @@ class FTPHandler(asynchat.async_chat):
|
||||
"""Delete the specified file."""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
self.run_as_current_user(self.fs.remove, path)
|
||||
datacr = self.fs.get_crdata(line, mode='delete')
|
||||
self.run_as_current_user(self.fs.remove, datacr)
|
||||
except OSError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL DELE "%s". %s.' %(line, why))
|
||||
@@ -2662,15 +2654,13 @@ class FTPHandler(asynchat.async_chat):
|
||||
here, see RNTO command)"""
|
||||
datacr = None
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
line = self.fs.ftpnorm(line)
|
||||
path = self.fs.ftp2fs(line, datacr)
|
||||
if not self.fs.lexists(path):
|
||||
datacr = self.fs.get_crdata(line, mode='rfnr')
|
||||
if not datacr[1]:
|
||||
self.respond("550 No such file or directory.")
|
||||
elif self.fs.realpath(path) == self.fs.realpath(self.fs.root):
|
||||
elif not datacr[1]:
|
||||
self.respond("550 Can't rename the home directory.")
|
||||
else:
|
||||
self.fs.rnfr = line
|
||||
self.fs.rnfr = datacr[1]
|
||||
self.respond("350 Ready for destination name.")
|
||||
except:
|
||||
self.respond("550 Can't find the file or directory.")
|
||||
@@ -2686,21 +2676,20 @@ class FTPHandler(asynchat.async_chat):
|
||||
datacr = None
|
||||
try:
|
||||
try:
|
||||
datacr = self.fs.get_cr(line)
|
||||
src = self.fs.ftp2fs(self.fs.rnfr, datacr)
|
||||
line = self.fs.ftpnorm(line)
|
||||
basedir,basename = os.path.split(line)
|
||||
dst = self.fs.ftp2fs(basedir, datacr)
|
||||
self.run_as_current_user(self.fs.rename, src, dst,basename)
|
||||
except OSError, err:
|
||||
datacr = self.fs.get_crdata(line,'create')
|
||||
oldname = self.fs.rnfr.path
|
||||
if isinstance(oldname, (list, tuple)):
|
||||
oldname = '/'.join(oldname)
|
||||
self.run_as_current_user(self.fs.rename, self.fs.rnfr, datacr)
|
||||
self.fs.rnfr = None
|
||||
self.log('OK RNFR/RNTO "%s ==> %s".' % \
|
||||
(_to_unicode(oldname), _to_unicode(line)))
|
||||
self.respond("250 Renaming ok.")
|
||||
except EnvironmentError, err:
|
||||
why = _strerror(err)
|
||||
self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \
|
||||
%(self.fs.ftpnorm(self.fs.rnfr), line, why))
|
||||
% (_to_unicode(oldname), _to_unicode(line), why))
|
||||
self.respond('550 %s.' %why)
|
||||
else:
|
||||
self.log('OK RNFR/RNTO "%s ==> %s".' \
|
||||
%(self.fs.ftpnorm(self.fs.rnfr), line))
|
||||
self.respond("250 Renaming ok.")
|
||||
finally:
|
||||
self.fs.rnfr = None
|
||||
self.fs.close_cr(datacr)
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
-
|
||||
In order to test the document_ftp functionality
|
||||
-
|
||||
I open the 8021 port and see for ftp presence there
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_plain_ftp()
|
||||
assert ftp.sock and (ftp.lastresp == '220'), ftp.lastresp
|
||||
-
|
||||
I read the list of databases at port 8021 and confirm our db is
|
||||
there
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_login(cr, uid, self)
|
||||
assert cr.dbname in ftp.nlst("/")
|
||||
-
|
||||
I try to locate the default "Documents" folder in the db.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_login(cr, uid, self)
|
||||
ftp.cwd('Documents')
|
||||
-
|
||||
I create a "test.txt" file at the server (directly). The file
|
||||
should have the "abcd" content
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR test.txt', fdata)
|
||||
-
|
||||
I look for the "test.txt" file at the server
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
assert ftp.nlst("test.txt") == ['test.txt']
|
||||
-
|
||||
I check that the content of "test.txt" is "abcd"
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
assert te.get_ftp_fulldata(ftp, "test.txt") == 'abcd'
|
||||
-
|
||||
I append the string 'defgh' into "test.txt"
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
fdata = StringIO('defgh')
|
||||
ftp.storbinary('APPE test.txt', fdata)
|
||||
-
|
||||
I check that the content of "text.txt" is 'abcddefgh'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
assert te.get_ftp_fulldata(ftp, "test.txt") == 'abcddefgh'
|
||||
-
|
||||
I try to cd into an non-existing folder 'Not-This'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
import ftplib
|
||||
ftp = te.get_ftp_login(cr, uid, self)
|
||||
try:
|
||||
ftp.cwd('/Not-This')
|
||||
assert False, "We should't be able to change here"
|
||||
except ftplib.error_perm:
|
||||
pass
|
||||
except OSError, err:
|
||||
assert err.errno == 2, err.errno
|
||||
-
|
||||
I create a "test2.txt" file through FTP.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR test2.txt', fdata)
|
||||
-
|
||||
I look for the "test2.txt" file at the server
|
||||
-
|
||||
!python {model: ir.attachment }: |
|
||||
ids = self.search(cr, uid, [('name', '=', 'test2.txt')])
|
||||
assert ids
|
||||
-
|
||||
I delete the "test2.txt" file using FTP.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
ftp.delete('test2.txt')
|
||||
-
|
||||
I check at the server that test2.txt is deleted
|
||||
-
|
||||
!python {model: ir.attachment }: |
|
||||
ids = self.search(cr, uid, [('name', '=', 'test2.txt')])
|
||||
assert not ids
|
||||
-
|
||||
I create a test2.txt file again.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR test2.txt', fdata)
|
||||
-
|
||||
I delete the test2.txt from the server (RPC).
|
||||
-
|
||||
!delete { model: ir.attachment, id:, search: "[('name','=','test2.txt')]" }
|
||||
-
|
||||
I check through FTP that test2.txt does not appear.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
assert ftp.nlst("test2.txt") == []
|
||||
-
|
||||
I create a "test-name.txt" file
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR test-name.txt', fdata)
|
||||
-
|
||||
I rename the "test-name.txt" file through ftp.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
ftp.rename("test-name.txt", "test-renamed.txt")
|
||||
-
|
||||
I check that test-name.txt has been renamed.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from ftplib import error_perm
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
try:
|
||||
res = ftp.nlst("test-name.txt")
|
||||
assert res == []
|
||||
except error_perm, e:
|
||||
pass
|
||||
assert ftp.nlst("test-renamed.txt") == ['test-renamed.txt']
|
||||
-
|
||||
I create a new folder 'Test-Folder2' through FTP
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
ftp.mkd("Test-Folder2")
|
||||
-
|
||||
I create a file 'test3.txt' at the 'Test-Folder2'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR test3.txt', fdata)
|
||||
-
|
||||
I try to retrieve test3.txt
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2')
|
||||
assert ftp.nlst("test3.txt") == ['test3.txt']
|
||||
-
|
||||
I create a new folder, 'Test-Folder3', through FTP
|
||||
I try to move test3.txt to 'Test-Folder3'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents')
|
||||
ftp.mkd("Test-Folder2")
|
||||
# TODO move
|
||||
-
|
||||
I remove the 'Test-Folder3'
|
||||
-
|
||||
I check that test3.txt is removed.
|
||||
-
|
||||
I create 200 files through FTP
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2')
|
||||
fdata = StringIO('abcd')
|
||||
# TODO speed
|
||||
for i in range(0, 200):
|
||||
fdata.seek(0)
|
||||
ftp.storbinary('STOR test-name%s.txt' %i, fdata)
|
||||
-
|
||||
I list the 200 files, check speed
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2')
|
||||
# TODO speed
|
||||
assert len(ftp.nlst()) > 200
|
||||
-
|
||||
I read the 200 files, check speed
|
||||
# TODO
|
||||
-
|
||||
I move the 200 files to 'Test-Folder2'
|
||||
# TODO
|
||||
-
|
||||
@@ -0,0 +1,78 @@
|
||||
-
|
||||
In order to check international character functionality
|
||||
-
|
||||
I create in the server a folder called 'Äïêéìáóôéêüò ÖÜêåëëïò'
|
||||
-
|
||||
!record {model: document.directory, id: dir_itests }:
|
||||
name: 'Äïêéìáóôéêüò ÖÜêåëëïò'
|
||||
parent_id: document.dir_root
|
||||
-
|
||||
And then I create another folder, under it, through FTP
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
cr.commit()
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò')
|
||||
ftp.mkd("ÖÜêåëëïò áðü êÜôù")
|
||||
-
|
||||
I check that this folder exists at the server
|
||||
-
|
||||
!assert {model: document.directory, id: , search: "[('name','=','ÖÜêåëëïò áðü êÜôù')]" }:
|
||||
- parent_id != False
|
||||
-
|
||||
I login with FTP and check that 'Äïêéìáóôéêüò ÖÜêåëëïò' is there
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
|
||||
-
|
||||
I create a file named 'ÄïêéìÞ' into that folder
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
|
||||
fdata = StringIO('êåßìåíï ìå utf-8')
|
||||
ftp.storbinary('STOR ÄïêéìÞ.txt', fdata)
|
||||
-
|
||||
I remove the 'ÄïêéìÞ.txt' file
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù')
|
||||
ftp.delete('ÄïêéìÞ.txt')
|
||||
-
|
||||
I rename 'ÖÜêåëëïò áðü êÜôù' into 'Üëëïò'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò')
|
||||
ftp.rename("ÖÜêåëëïò áðü êÜôù", "Üëëïò")
|
||||
-
|
||||
I place a file 'file Ö3' in 'Üëëïò'
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
|
||||
fdata = StringIO('êé Üëëï êåßìåíï')
|
||||
ftp.storbinary('STOR file Ö3.txt', fdata)
|
||||
-
|
||||
I rename the file into file+range(1..200) (large filename)
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
|
||||
vuvuzela = 'b'+''.join('z' * 200)+'!'
|
||||
ftp.rename("file Ö3.txt", vuvuzela)
|
||||
-
|
||||
I delete the file with the large name
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò')
|
||||
vuvuzela = 'b'+''.join('z' * 200)+'!'
|
||||
ftp.delete(vuvuzela)
|
||||
@@ -0,0 +1,116 @@
|
||||
-
|
||||
In order to check dynamic folder functionality of document + FTP
|
||||
- |
|
||||
I create two partners 'Partner1' and 'Partner2'.
|
||||
I create three partner categories: 'none', 'pat1' and 'all'
|
||||
I attach Partner1 to pat1, Partner1+Partner2 to 'all'
|
||||
-
|
||||
!record {model: res.partner.category, id: tpat_categ_none }:
|
||||
name: 'No partners'
|
||||
-
|
||||
!record {model: res.partner.category, id: tpat_categ_pat1 }:
|
||||
name: 'Pat 1'
|
||||
-
|
||||
!record {model: res.partner.category, id: tpat_categ_all }:
|
||||
name: 'All Partner1+2'
|
||||
-
|
||||
!record {model: res.partner, id: tpartner1 }:
|
||||
name: Partner 1
|
||||
category_id:
|
||||
- tpat_categ_pat1
|
||||
- tpat_categ_all
|
||||
-
|
||||
!record {model: res.partner, id: tpartner_2 }:
|
||||
name: 'Partner 2'
|
||||
category_id:
|
||||
- tpat_categ_all
|
||||
-
|
||||
I create a resource folder of partners, by the (none, pat1, all)
|
||||
categories.
|
||||
-
|
||||
!record {model: document.directory, id: dir_tests2 }:
|
||||
name: Partners Testing
|
||||
parent_id: document.dir_root
|
||||
type: ressource
|
||||
ressource_type_id: base.model_res_partner_category
|
||||
domain: [] # TODO
|
||||
-
|
||||
I commit (because FTP operations are on different transaction)
|
||||
-
|
||||
!python {model: document.directory, id: }: |
|
||||
cr.commit()
|
||||
-
|
||||
I browse through ftp in the resource folder, checking that three
|
||||
categories are there.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing')
|
||||
dirs = ftp.nlst()
|
||||
for dir in [ 'All Partner1+2', 'No partners', 'Pat 1' ]:
|
||||
assert dir in dirs, "Dir %s not in folder" % dir
|
||||
-
|
||||
I create a 'partners' folder by the first resource one.
|
||||
-
|
||||
!record {model: document.directory, id: dir_respart1 }:
|
||||
name: Partners of Test
|
||||
parent_id: dir_tests2
|
||||
type: ressource
|
||||
ressource_type_id: base.model_res_partner
|
||||
domain: "[('category_id','in',[active_id])]"
|
||||
ressource_parent_type_id : base.model_res_partner_category
|
||||
-
|
||||
!python {model: document.directory, id: }: |
|
||||
cr.commit()
|
||||
-
|
||||
I check through FTP that the correct partners are listed at each
|
||||
'partners' folder.
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing')
|
||||
correct = { 'All Partner1+2': [ 'Partner 1', 'Partner 2' ],
|
||||
'No partners': [],
|
||||
'Pat 1': ['Partner 1',] }
|
||||
for dir in correct:
|
||||
res = ftp.nlst(dir+'/Partners of Test')
|
||||
assert res == correct[dir], "Dir %s falsely contains %s" %(dir, res)
|
||||
-
|
||||
I create an ir.attachment, attached (not related) to Partner1
|
||||
-
|
||||
!record {model: ir.attachment, id: file_test1 }:
|
||||
name: File of pat1
|
||||
res_model: res.partner
|
||||
res_id: !eval ref("tpartner1")
|
||||
-
|
||||
I check that pat1/Partner1 folder has the file.
|
||||
I check that all/Partner1 folder has the file
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing')
|
||||
dirs = [ 'All Partner1+2', 'Pat 1' ]
|
||||
for dir in dirs:
|
||||
res = ftp.nlst(dir+'/Partners of Test/Partner 1')
|
||||
assert 'File of pat1' in res, "Dir %s contains only %s" %(dir, res)
|
||||
-
|
||||
I place a file at the 'pat1'/Partner1 folder, through FTP
|
||||
-
|
||||
!python {model: ir.attachment}: |
|
||||
from document_ftp import test_easyftp as te
|
||||
from cStringIO import StringIO
|
||||
ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Partners Testing/Pat 1/Partners of Test/Partner 1')
|
||||
fdata = StringIO('abcd')
|
||||
ftp.storbinary('STOR pat1-dynamic.txt', fdata)
|
||||
-
|
||||
I check at the server that the file is attached to Partner1
|
||||
-
|
||||
!assert {model: ir.attachment, id: , search: "[('name','=','pat1-dynamic.txt')]" }:
|
||||
- parent_id.name == 'Partners of Test'
|
||||
- res_model == 'res.partner'
|
||||
- res_id != False
|
||||
-
|
||||
I check that all/Partner1 also has the file
|
||||
- |
|
||||
Bonus Piste:
|
||||
I create a 'Partner3' under 'all'
|
||||
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
""" This is a testing module, which exports some functions for the YAML tests.
|
||||
Instead of repeating the same FTP code all over, we prefer to have
|
||||
it in this file
|
||||
"""
|
||||
|
||||
from ftplib import FTP
|
||||
from tools.misc import detect_ip_addr
|
||||
from tools import config
|
||||
|
||||
def get_plain_ftp():
|
||||
ftp = FTP()
|
||||
host = config.get('ftp_server_host', '127.0.0.1')
|
||||
port = config.get('ftp_server_port','8021')
|
||||
ftp.connect(host,port)
|
||||
return ftp
|
||||
|
||||
def get_ftp_login(cr, uid, ormobj):
|
||||
ftp = get_plain_ftp()
|
||||
user = ormobj.pool.get('res.users').read(cr, uid, uid)
|
||||
ftp.login(user.get('login',''),user.get('password',''))
|
||||
ftp.cwd("/" + cr.dbname)
|
||||
return ftp
|
||||
|
||||
def get_ftp_anonymous(cr):
|
||||
ftp = get_plain_ftp()
|
||||
ftp.login('anonymous', 'the-test')
|
||||
ftp.cwd("/")
|
||||
return ftp
|
||||
|
||||
def get_ftp_folder(cr, uid, ormobj, foldername):
|
||||
ftp = get_ftp_login(cr, uid, ormobj)
|
||||
ftp.cwd("/" + cr.dbname+"/"+foldername)
|
||||
return ftp
|
||||
|
||||
def get_ftp_fulldata(ftp, fname, limit=8192):
|
||||
from functools import partial
|
||||
data = []
|
||||
def ffp(data, ndata):
|
||||
if len(data)+ len(ndata) > limit:
|
||||
raise IndexError('Data over the limit')
|
||||
data.append(ndata)
|
||||
ftp.retrbinary('RETR %s' % fname, partial(ffp,data))
|
||||
return ''.join(data)
|
||||
|
||||
#eof
|
||||
@@ -48,6 +48,8 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
|
||||
# write href information
|
||||
uparts=urlparse.urlparse(uri)
|
||||
fileloc=uparts[2]
|
||||
if isinstance(fileloc, unicode):
|
||||
fileloc = fileloc.encode('utf-8')
|
||||
href=doc.createElement("D:href")
|
||||
davpath = self._dataclass.parent.get_davpath()
|
||||
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
|
||||
@@ -126,6 +128,8 @@ def mk_propname_response(self,uri,propnames,doc):
|
||||
# write href information
|
||||
uparts=urlparse.urlparse(uri)
|
||||
fileloc=uparts[2]
|
||||
if isinstance(fileloc, unicode):
|
||||
fileloc = fileloc.encode('utf-8')
|
||||
href=doc.createElement("D:href")
|
||||
davpath = self._dataclass.parent.get_davpath()
|
||||
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
|
||||
|
||||
Reference in New Issue
Block a user