import tools
from tools.translate import _
import nodes
+import logging
DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['root_path'], 'filestore'))
class document_file(osv.osv):
_inherit = 'ir.attachment'
_rec_name = 'datas_fname'
+
+ def _attach_parent_id(self, cr, uid, ids=None, context=None):
+ """Migrate ir.attachments to the document module.
+
+ When the 'document' module is loaded on a db that has had plain attachments,
+ they will need to be attached to some parent folder, and be converted from
+ base64-in-bytea to raw-in-bytea format.
+ This function performs the internal migration, once and forever, for these
+ attachments. It cannot be done through the nominal ORM maintenance code,
+ because the root folder is only created after the document_data.xml file
+ is loaded.
+ It also establishes the parent_id NOT NULL constraint that ir.attachment
+ should have had (but would have failed if plain attachments contained null
+ values).
+ It also updates the File Size for the previously created attachments.
+ """
+
+ parent_id = self.pool.get('document.directory')._get_root_directory(cr,uid)
+ if not parent_id:
+ logging.getLogger('document').warning("at _attach_parent_id(), still not able to set the parent!")
+ return False
+
+ if ids is not None:
+ raise NotImplementedError("Ids is just there by convention! Don't use it yet, please.")
+
+ cr.execute("UPDATE ir_attachment " \
+ "SET parent_id = %s, db_datas = decode(encode(db_datas,'escape'), 'base64') " \
+ "WHERE parent_id IS NULL", (parent_id,))
+
+ cr.execute("UPDATE ir_attachment SET file_size=length(db_datas) WHERE file_size = 0;")
+
+ cr.execute("ALTER TABLE ir_attachment ALTER parent_id SET NOT NULL")
+
+ return True
+
def _get_filestore(self, cr):
return os.path.join(DMS_ROOT_PATH, cr.dbname)
- def _data_get(self, cr, uid, ids, name, arg, context):
+ def _data_get(self, cr, uid, ids, name, arg, context=None):
+ if context is None:
+ context = {}
fbrl = self.browse(cr, uid, ids, context=context)
nctx = nodes.get_node_context(cr, uid, context={})
# nctx will /not/ inherit the caller's context. Most of
#
# This code can be improved
#
- def _data_set(self, cr, uid, id, name, value, arg, context):
+ def _data_set(self, cr, uid, id, name, value, arg, context=None):
if not value:
return True
fbro = self.browse(cr, uid, id, context=context)
'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_model': fields.char('Attached Model', size=64, readonly=True, change_default=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'),
- 'datas': fields.function(_data_get, method=True, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True),
+ 'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True),
# 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),
+ 'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True, change_default=True),
'index_content': fields.text('Indexed Content'),
'partner_id':fields.many2one('res.partner', 'Partner', select=1),
'file_size': fields.integer('File Size', required=True),
'file_type': fields.char('Content Type', size=128),
- 'company_id': fields.many2one('res.company', 'Company'),
+
# fields used for file storage
'store_fname': fields.char('Stored Filename', size=200),
}
return dirobj._get_root_directory(cr, uid, context)
_defaults = {
- '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,
'parent_id': __get_def_directory
}
_sql_constraints = [
- ('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
+ # filename_uniq is not possible in pure SQL
]
def _check_duplication(self, cr, uid, vals, ids=[], op='create'):
name = vals.get('name', False)
return False
return True
+ def check(self, cr, uid, ids, mode, context=None, values=None):
+ """Check access wrt. res_model, relax the rule of ir.attachment parent
+
+ With 'document' installed, everybody will have access to attachments of
+ any resources they can *read*.
+ """
+ return super(document_file, self).check(cr, uid, ids, mode='read',
+ context=context, values=values)
+
+ def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
+ # Grab ids, bypassing 'count'
+ ids = super(document_file, self).search(cr, uid, args, offset=offset,
+ limit=limit, order=order,
+ context=context, count=False)
+ if not ids:
+ return 0 if count else []
+
+ # Filter out documents that are in directories that the user is not allowed to read.
+ # Must use pure SQL to avoid access rules exceptions (we want to remove the records,
+ # not fail), and the records have been filtered in parent's search() anyway.
+ cr.execute('SELECT id, parent_id from "%s" WHERE id in %%s' % self._table, (tuple(ids),))
+ doc_pairs = cr.fetchall()
+ parent_ids = set(zip(*doc_pairs)[1])
+ visible_parent_ids = self.pool.get('document.directory').search(cr, uid, [('id', 'in', list(parent_ids))])
+ disallowed_parents = parent_ids.difference(visible_parent_ids)
+ for doc_id, parent_id in doc_pairs:
+ if parent_id in disallowed_parents:
+ ids.remove(doc_id)
+ return len(ids) if count else ids
+
+
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
if 'name' not in default:
- name = self.read(cr, uid, [id])[0]['name']
- default.update({'name': name + " (copy)"})
- return super(document_file, self).copy(cr, uid, id, default, context)
+ name = self.read(cr, uid, [id], ['name'])[0]['name']
+ default.update({'name': name + " " + _("(copy)")})
+ return super(document_file, self).copy(cr, uid, id, default, context=context)
def write(self, cr, uid, ids, vals, context=None):
result = False
ids2 = []
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']) :
+ and ('name' not in vals or fbro.name == vals['name']):
ids2.append(fbro.id)
continue
fnode = nctx.get_file_node(cr, fbro)
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() # ?
+ result = self._check_duplication(cr, uid, vals)
+ if not result:
+ domain = [
+ ('res_id', '=', vals['res_id']),
+ ('res_model', '=', vals['res_model']),
+ ('datas_fname', '=', vals['datas_fname']),
+ ]
+ attach_ids = self.search(cr, uid, domain, context=context)
+ super(document_file, self).write(cr, uid, attach_ids,
+ {'datas' : vals['datas']},
+ context=context)
+ result = attach_ids[0]
+ else:
+ #raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
+ result = super(document_file, self).create(cr, uid, vals, context)
+ cr.commit() # ?
return result
- def __get_partner_id(self, cr, uid, res_model, res_id, context):
+ def __get_partner_id(self, cr, uid, res_model, res_id, context=None):
""" 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.
# files to be unlinked, update the db (safer to do first, can be
# rolled back) and then unlink the files. The list wouldn't exist
# after we discard the objects
-
- for f in self.browse(cr, uid, ids, context):
+ ids = self.search(cr, uid, [('id','in',ids)])
+ for f in self.browse(cr, uid, ids, context=context):
# TODO: update the node cache
par = f.parent_id
storage_id = None
r = stor.prepare_unlink(cr, uid, storage_id, f)
if r:
unres.append(r)
+ else:
+ logging.getLogger('document').warning("Unlinking attachment #%s %s that has no storage",
+ f.id, f.name)
res = super(document_file, self).unlink(cr, uid, ids, context)
stor.do_unlink(cr, uid, unres)
return res