bzr revid: hmo@tinyerp.com-20100713143141-wozs7fnurj8ckblw
This commit is contained in:
Harry (OpenERP)
2010-07-13 20:01:41 +05:30
12 changed files with 241 additions and 66 deletions
+17 -4
View File
@@ -58,7 +58,9 @@ class document_directory_content(osv.osv):
'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."),
'include_name': fields.boolean('Include Record Name',
help="Check this field if you want that the name of the file to contain the record name." \
"\nIf set, the directory will have to be a resource one."),
'directory_id': fields.many2one('document.directory', 'Directory'),
}
_defaults = {
@@ -88,12 +90,19 @@ class document_directory_content(osv.osv):
tname = (content.prefix or '') + (content.suffix or '') + (content.extension or '')
if tname.find('/'):
tname=tname.replace('/', '_')
act_id = False
if 'dctx_res_id' in node.dctx:
act_id = node.dctx['dctx_res_id']
elif hasattr(node, 'res_id'):
act_id = node.res_id
else:
act_id = node.context.context.get('res_id',False)
if not nodename:
n = nodes.node_content(tname, node, node.context,content)
n = nodes.node_content(tname, node, node.context,content, act_id=act_id)
res2.append( n)
else:
if nodename == tname:
n = nodes.node_content(tname, node, node.context,content)
n = nodes.node_content(tname, node, node.context,content, act_id=act_id)
n.fill_fields(cr)
res2.append(n)
return res2
@@ -108,6 +117,10 @@ class document_directory_content(osv.osv):
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']], {}, {})
ctx = node.context.context.copy()
ctx.update(node.dctx)
pdf,pdftype = srv.create(cr, uid, [node.act_id,], {}, context=ctx)
return pdf
document_directory_content()
#eof
+6 -3
View File
@@ -47,7 +47,10 @@ class document_file(osv.osv):
def _data_get(self, cr, uid, ids, name, arg, context):
fbrl = self.browse(cr, uid, ids, context=context)
nctx = nodes.get_node_context(cr, uid, context)
nctx = nodes.get_node_context(cr, uid, context={})
# nctx will /not/ inherit the caller's context. Most of
# it would be useless, anyway (like active_id, active_model,
# bin_size etc.)
result = {}
bin_size = context.get('bin_size', False)
for fbro in fbrl:
@@ -67,7 +70,7 @@ class document_file(osv.osv):
if not value:
return True
fbro = self.browse(cr, uid, id, context=context)
nctx = nodes.get_node_context(cr, uid, context)
nctx = nodes.get_node_context(cr, uid, context={})
fnode = nodes.node_file(None, None, nctx, fbro)
res = fnode.set_data(cr, base64.decodestring(value), fbro)
return res
@@ -156,7 +159,7 @@ class document_file(osv.osv):
raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
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)
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)
+4
View File
@@ -57,6 +57,9 @@ class document_directory(osv.osv):
'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.'),
'resource_find_all': fields.boolean('Find all resources', required=True,
help="If true, all attachments that match this resource will " \
" be located. If false, only ones that have this as parent." ),
'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."),
@@ -103,6 +106,7 @@ class document_directory(osv.osv):
'type': lambda *args: 'directory',
'ressource_id': lambda *a: 0,
'storage_id': _get_def_storage,
'resource_find_all': True,
}
_sql_constraints = [
('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
+2 -1
View File
@@ -82,6 +82,7 @@
<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')] }"/>
<field name="resource_find_all" groups="base.group_extended" />
<newline/>
<field name="resource_field" domain="[('model_id','=',ressource_type_id), ('ttype', 'in', ('char', 'selection', 'date', 'datetime'))]"/>
<field name="ressource_tree"/>
@@ -96,7 +97,7 @@
</page>
<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)]}">
<field name="content_ids" nolabel="1" colspan="4" >
<form string="Contents">
<field name="name"/>
<field name="sequence"/>
+101 -13
View File
@@ -31,6 +31,8 @@ import errno
import os
import time
from StringIO import StringIO
#
# An object that represent an uri
# path: the uri of the object
@@ -226,7 +228,7 @@ class node_class(object):
return False
def get_data(self,cr):
raise IOError(errno.EINVAL, 'no data for %s' % self.type)
raise TypeError('no data for %s'% self.type)
def open_data(self, cr, mode):
""" Open a node_descriptor object for this node.
@@ -240,7 +242,7 @@ class node_class(object):
For this class, there is no data, so no implementation. Each
child class that has data should override this.
"""
raise IOError(errno.EINVAL, 'no data for %s' % self.type)
raise TypeError('no data for %s' % self.type)
def _get_storage(self,cr):
raise RuntimeError("no storage for base class")
@@ -306,13 +308,23 @@ class node_class(object):
Move operations, as instructed from APIs (eg. request from DAV) could
use this function.
"""
raise NotImplementedError
raise NotImplementedError(repr(self))
def create_child(self, cr, path, data=None):
""" Create a regular file under this node
"""
raise NotImplementedError(repr(self))
def create_child_collection(self, cr, objname):
""" Create a child collection (directory) under self
"""
raise NotImplementedError(repr(self))
def rm(self, cr):
raise RuntimeError("Not Implemented")
raise NotImplementedError(repr(self))
def rmcol(self, cr):
raise RuntimeError("Not Implemented")
raise NotImplementedError(repr(self))
def get_domain(self, cr, filters):
return []
@@ -535,7 +547,7 @@ class node_dir(node_database):
return dirobj.create(cr, uid, val)
def create_child(self, cr, path, data):
def create_child(self, cr, path, data=None):
""" API function to create a child file object and node
Return the node_* created
"""
@@ -647,6 +659,7 @@ class node_res_dir(node_class):
self.uidperms = dirr.get_dir_permissions()
self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
self.resm_id = dirr.ressource_id
self.res_find_all = dirr.resource_find_all
self.namefield = dirr.resource_field.name or 'name'
self.displayname = dirr.name
# Important: the domain is evaluated using the *parent* dctx!
@@ -761,16 +774,17 @@ class node_res_obj(node_class):
self.write_date = parent.write_date
self.content_length = 0
self.unixperms = 040750
self.uidperms = parent.uidperms & 0x15
self.uidperms = parent.uidperms & 15
self.uuser = parent.uuser
self.ugroup = parent.ugroup
self.res_model = res_model
self.domain = parent.domain
self.displayname = path
self.dctx_dict = parent.dctx_dict
self.res_find_all = parent.res_find_all
if res_bo:
self.res_id = res_bo.id
dc2 = self.context.context
dc2 = self.context.context.copy()
dc2.update(self.dctx)
dc2['res_model'] = res_model
dc2['res_id'] = res_bo.id
@@ -796,6 +810,8 @@ class node_res_obj(node_class):
return False
if self.domain != other.domain:
return False
if self.res_find_all != other.res_find_all:
return False
if self.dctx != other.dctx:
return False
return self.dir_id == other.dir_id
@@ -909,6 +925,8 @@ class node_res_obj(node_class):
fil_obj = dirobj.pool.get('ir.attachment')
if self.res_find_all:
where2 = where
where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
# print "where clause for dir_obj", where2
ids = fil_obj.search(cr, uid, where3, context=ctx)
@@ -960,7 +978,7 @@ class node_res_obj(node_class):
return dirobj.create(cr, uid, val)
def create_child(self, cr, path, data):
def create_child(self, cr, path, data=None):
""" API function to create a child file object and node
Return the node_* created
"""
@@ -976,11 +994,12 @@ class node_res_obj(node_class):
val = {
'name': path,
'datas_fname': path,
'parent_id': self.dir_id,
'res_model': self.res_model,
'res_id': self.res_id,
# Datas are not set here
}
if not self.res_find_all:
val['parent_id'] = self.dir_id
fil_id = fil_obj.create(cr, uid, val, context=ctx)
fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
@@ -1204,9 +1223,6 @@ class node_content(node_class):
self.dctx.update(dctx)
self.act_id = act_id
def open(self, cr, mode=False):
raise DeprecationWarning()
def fill_fields(self, cr, dctx = None):
""" Try to read the object and fill missing fields, like mimetype,
dates etc.
@@ -1233,7 +1249,29 @@ class node_content(node_class):
self.content_length = len(data)
return data
def open_data(self, cr, mode):
if mode.endswith('b'):
mode = mode[:-1]
if mode in ('r', 'w'):
cperms = mode[:1]
elif mode in ('r+', 'w+'):
cperms = 'rw'
else:
raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
if not self.check_perms(cperms):
raise IOError(errno.EPERM, "Permission denied")
ctx = self.context.context.copy()
ctx.update(self.dctx)
return nodefd_content(self, cr, mode, ctx)
def get_data_len(self, cr, fil_obj = None):
# FIXME : here, we actually generate the content twice!!
# we should have cached the generated content, but it is
# not advisable to do keep it in memory, until we have a cache
# expiration logic.
if not self.content_length:
self.get_data(cr,fil_obj)
return self.content_length
@@ -1249,3 +1287,53 @@ class node_content(node_class):
def _get_ttag(self,cr):
return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
class nodefd_content(StringIO, node_descriptor):
""" A descriptor to content nodes
"""
def __init__(self, parent, cr, mode, ctx):
node_descriptor.__init__(self, parent)
self._context=ctx
if mode in ('r', 'r+'):
cntobj = parent.context._dirobj.pool.get('document.directory.content')
data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
if data:
parent.content_length = len(data)
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.content').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
if self.mode == 'r':
StringIO.close(self)
return
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()
cntobj = par.context._dirobj.pool.get('document.directory.content')
cntobj.process_write(cr, uid, parent, data, ctx)
elif self.mode == 'a':
raise NotImplementedError
cr.commit()
except Exception, e:
logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
raise
finally:
cr.close()
StringIO.close(self)
@@ -169,7 +169,10 @@ class abstracted_fs(object):
raise OSError(1, 'Operation not permited.')
# Reading operation
cr, node, rem = datacr
res = node.open_data(cr, mode)
try:
res = node.open_data(cr, mode)
except TypeError, e:
raise IOError(errno.EINVAL, "No data")
return res
# ok, but need test more
@@ -427,7 +430,7 @@ class abstracted_fs(object):
if not (datacr and datacr[1]):
return 0L
if datacr[1].type in ('file', 'content'):
return datacr[1].content_length or 0L
return datacr[1].get_data_len(datacr[0]) or 0L
return 0L
# Ok
@@ -5,7 +5,7 @@
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_plain_ftp()
ftp = te.get_plain_ftp(timeout=2.0)
assert ftp.sock and (ftp.lastresp == '220'), ftp.lastresp
-
I read the list of databases at port 8021 and confirm our db is
@@ -1,5 +1,9 @@
-
In order to check international character functionality
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_plain_ftp(timeout=1.0)
-
I create in the server a folder called 'Äïêéìáóôéêüò ÖÜêåëëïò'
-
@@ -1,5 +1,9 @@
-
In order to check dynamic folder functionality of document + FTP
-
!python {model: ir.attachment}: |
from document_ftp import test_easyftp as te
ftp = te.get_plain_ftp(timeout=1.0)
- |
I create two partners 'Partner1' and 'Partner2'.
I create three partner categories: 'none', 'pat1' and 'all'
+2 -2
View File
@@ -28,11 +28,11 @@ from ftplib import FTP
from tools.misc import detect_ip_addr
from tools import config
def get_plain_ftp():
def get_plain_ftp(timeout=10.0):
ftp = FTP()
host = config.get('ftp_server_host', '127.0.0.1')
port = config.get('ftp_server_port','8021')
ftp.connect(host,port)
ftp.connect(host,port, timeout=timeout)
return ftp
def get_ftp_login(cr, uid, ormobj):
+78 -40
View File
@@ -46,6 +46,15 @@ CACHE_SIZE=20000
urlparse.uses_netloc.append('webdav')
urlparse.uses_netloc.append('webdavs')
class DAV_NotFound2(DAV_NotFound):
"""404 exception, that accepts our list uris
"""
def __init__(self, *args):
if len(args) and isinstance(args[0], (tuple, list)):
path = ''.join([ '/' + x for x in args[0]])
args = (path, )
DAV_NotFound.__init__(self, *args)
class openerp_dav_handler(dav_interface):
"""
This class models a OpenERP interface for the DAV server
@@ -74,6 +83,38 @@ class openerp_dav_handler(dav_interface):
cr.close()
return props
def _try_function(self, funct, args, opname='run function', cr=None,
default_exc=DAV_Forbidden):
""" Try to run a function, and properly convert exceptions to DAV ones.
@objname the name of the operation being performed
@param cr if given, the cursor to close at exceptions
"""
try:
funct(*args)
except DAV_Error:
if cr: cr.close()
raise
except NotImplementedError, e:
if cr: cr.close()
import traceback
self.parent.log_error("Cannot %s: %s", opname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
# see par 9.3.1 of rfc
raise DAV_Error(403, str(e) or 'Not supported at this path')
except EnvironmentError, err:
if cr: cr.close()
import traceback
self.parent.log_error("Cannot %s: %s", opname, e.strerror)
self.parent.log_message("Exc: %s",traceback.format_exc())
raise default_exc(err.strerror)
except Exception,e:
import traceback
self.parent.log_error("Cannot create %s: %s", opname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise default_exc("Operation failed")
def _get_dav_lockdiscovery(self, uri):
raise DAV_NotFound
@@ -192,7 +233,7 @@ class openerp_dav_handler(dav_interface):
if not node:
if cr: cr.close()
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
else:
fp = node.full_path()
if fp and len(fp):
@@ -255,26 +296,25 @@ class openerp_dav_handler(dav_interface):
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
try:
if rrange:
self.parent.log_error("Doc get_data cannot use range")
raise DAV_Error(409)
datas = node.get_data(cr)
except TypeError,e:
import traceback
self.parent.log_error("GET typeError: %s", str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
# for the collections that return this error, the DAV standard
# says we'd better just return 200 OK with empty data
return ''
except IndexError,e :
self.parent.log_error("GET IndexError: %s", str(e))
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
except Exception,e:
import traceback
self.parent.log_error("GET exception: %s",str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Error, 409
return datas
return str(datas) # FIXME!
finally:
if cr: cr.close()
@@ -288,7 +328,7 @@ class openerp_dav_handler(dav_interface):
return COLLECTION
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
if node.type in ('collection','database'):
return COLLECTION
return OBJECT
@@ -304,7 +344,7 @@ class openerp_dav_handler(dav_interface):
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
cr.close()
return node.displayname
@@ -320,7 +360,7 @@ class openerp_dav_handler(dav_interface):
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
result = node.content_length or 0
cr.close()
return str(result)
@@ -337,7 +377,7 @@ class openerp_dav_handler(dav_interface):
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
result = node.get_etag(cr)
cr.close()
return str(result)
@@ -352,7 +392,7 @@ class openerp_dav_handler(dav_interface):
try:
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
if node.write_date:
return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S'))
else:
@@ -369,7 +409,7 @@ class openerp_dav_handler(dav_interface):
try:
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
if node.create_date:
result = time.mktime(time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S'))
else:
@@ -387,7 +427,7 @@ class openerp_dav_handler(dav_interface):
try:
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound2(uri2)
result = str(node.mimetype)
return result
#raise DAV_NotFound, 'Could not find %s' % path
@@ -395,20 +435,28 @@ class openerp_dav_handler(dav_interface):
if cr: cr.close()
def mkcol(self,uri):
""" create a new collection """
""" create a new collection
see par. 9.3 of rfc4918
"""
self.parent.log_message('MKCOL: %s' % uri)
uri = self.uri2local(uri)[1:]
if uri[-1]=='/':uri=uri[:-1]
parent = '/'.join(uri.split('/')[:-1])
parent = self.baseuri + parent
uri = self.baseuri + uri
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not uri2[-1]:
cr.close()
raise DAV_Error(409, "Cannot create nameless collection")
if not dbname:
cr.close()
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2[:-1])
if node:
node.create_child_collection(cr, uri2[-1])
cr.commit()
if not node:
cr.close()
raise DAV_Error(409, "Parent path %s does not exist" % uri2[:-1])
nc = node.child(cr, uri2[-1])
if nc:
cr.close()
raise DAV_Error(405, "Path already exists")
self._try_function(node.create_child_collection, (cr, uri2[-1]),
"create col %s" % uri2[-1], cr=cr)
cr.commit()
cr.close()
return True
@@ -430,22 +478,13 @@ class openerp_dav_handler(dav_interface):
if not node:
dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
if not dir_node:
cr.close()
raise DAV_NotFound('Parent folder not found')
try:
dir_node.create_child(cr, objname, data)
except Exception,e:
import traceback
self.parent.log_error("Cannot create %s: %s", objname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
self._try_function(dir_node.create_child, (cr, objname, data),
"create %s" % objname, cr=cr)
else:
try:
node.set_data(cr, data)
except Exception,e:
import traceback
self.parent.log_error("Cannot save %s: %s", objname, str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)
cr.commit()
cr.close()
@@ -618,7 +657,6 @@ class openerp_dav_handler(dav_interface):
advanced systems we might also have to copy properties from
the source to the destination.
"""
print " copy a collection."
return self.mkcol(dst)
+17
View File
@@ -78,6 +78,18 @@ class DAVHandler(FixSendError,DAVRequestHandler):
self.baseuri = "http://%s:%d/"% (self.server.server_name, self.server.server_port)
self.IFACE_CLASS = openerp_dav_handler(self, self.verbose)
def copymove(self, CLASS):
""" Our uri scheme removes the /webdav/ component from there, so we
need to mangle the header, too.
"""
dest = self.headers['Destination']
up = urlparse.urlparse(urllib.unquote(self.headers['Destination']))
if up.path.startswith(self.davpath):
self.headers['Destination'] = up.path[len(self.davpath):]
else:
raise DAV_Forbidden("Not allowed to copy/move outside webdav path")
DAVRequestHandler.copymove(self, CLASS)
def get_davpath(self):
return self.davpath
@@ -175,6 +187,11 @@ class DAVHandler(FixSendError,DAVRequestHandler):
self.send_body(None, '201', 'Created', '', headers=headers)
def do_DELETE(self):
try:
DAVRequestHandler.do_DELETE(self)
except DAV_Error, (ec, dd):
return self.send_status(ec)
from service.http_server import reg_http_service,OpenERPAuthProvider