[ADD] fields: add option to store binary fields into attachments
Such a field is declared as:
image = fields.Binary("Image", attachment=True)
With this option, the value of a binary field F is stored in an attachment with
`res_field=F`. When attachments are stored into the filestore, binary fields
are consequently stored in the filestore as well. Note that the option does
not work on old-API function fields.
By default searching on `ir.attachment` filters out attachments for binary
fields: the condition `res_field=False` is added to the domain unless a
condition on field `id` or `res_field` is already present. The client uses
`search_read` with a domain like `[('id', 'in', ids)]` for reading the data on
a form view. In that case, we should not filter out attachments that store
binary fields.
Also, do not filter search results for the superuser.
This commit is contained in:
@@ -274,6 +274,7 @@ class ir_attachment(osv.osv):
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char', string='Resource Name', store=True),
|
||||
'res_model': fields.char('Resource Model', readonly=True, help="The database object this attachment will be attached to"),
|
||||
'res_field': fields.char('Resource Field', readonly=True),
|
||||
'res_id': fields.integer('Resource ID', readonly=True, help="The record id this is attached to"),
|
||||
'create_date': fields.datetime('Date Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
|
||||
@@ -352,14 +353,22 @@ class ir_attachment(osv.osv):
|
||||
raise AccessError(_("Sorry, you are not allowed to access this document."))
|
||||
|
||||
def _search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
|
||||
# add res_field=False in domain if not present; the arg[0] trick below
|
||||
# works for domain items and '&'/'|'/'!' operators too
|
||||
if not any(arg[0] in ('id', 'res_field') for arg in args):
|
||||
args.insert(0, ('res_field', '=', False))
|
||||
|
||||
ids = super(ir_attachment, self)._search(cr, uid, args, offset=offset,
|
||||
limit=limit, order=order,
|
||||
context=context, count=False,
|
||||
access_rights_uid=access_rights_uid)
|
||||
|
||||
if uid == SUPERUSER_ID:
|
||||
# rules do not apply for the superuser
|
||||
return len(ids) if count else ids
|
||||
|
||||
if not ids:
|
||||
if count:
|
||||
return 0
|
||||
return []
|
||||
return 0 if count else []
|
||||
|
||||
# Work with a set, as list.remove() is prohibitive for large lists of documents
|
||||
# (takes 20+ seconds on a db with 100k docs during search_count()!)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</group>
|
||||
<group string="Attached To" groups="base.group_no_one">
|
||||
<field name="res_model"/>
|
||||
<field name="res_field"/>
|
||||
<field name="res_id"/>
|
||||
<field name="res_name"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
@@ -51,6 +52,7 @@
|
||||
<field name="name"/>
|
||||
<field name="datas_fname"/>
|
||||
<field name="res_model"/>
|
||||
<field name="res_field"/>
|
||||
<field name="res_id"/>
|
||||
<field name="type"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
|
||||
@@ -1398,6 +1398,12 @@ class Datetime(Field):
|
||||
|
||||
class Binary(Field):
|
||||
type = 'binary'
|
||||
_slots = {
|
||||
'attachment': False, # whether value is stored in attachment
|
||||
}
|
||||
|
||||
_column_attachment = property(attrgetter('attachment'))
|
||||
_description_attachment = property(attrgetter('attachment'))
|
||||
|
||||
|
||||
class Selection(Field):
|
||||
|
||||
+60
-22
@@ -572,6 +572,7 @@ class datetime(_column):
|
||||
class binary(_column):
|
||||
_type = 'binary'
|
||||
_classic_read = False
|
||||
_classic_write = property(lambda self: not self.attachment)
|
||||
|
||||
# Binary values may be byte strings (python 2.6 byte array), but
|
||||
# the legacy OpenERP convention is to transfer and store binaries
|
||||
@@ -584,35 +585,69 @@ class binary(_column):
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = lambda self, x: x and str(x)
|
||||
|
||||
__slots__ = ['filters']
|
||||
__slots__ = ['attachment', 'filters']
|
||||
|
||||
def __init__(self, string='unknown', filters=None, **args):
|
||||
args['_prefetch'] = args.get('_prefetch', False)
|
||||
args['attachment'] = args.get('attachment', False)
|
||||
_column.__init__(self, string=string, filters=filters, **args)
|
||||
|
||||
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
||||
if not context:
|
||||
context = {}
|
||||
if not values:
|
||||
values = []
|
||||
res = {}
|
||||
for i in ids:
|
||||
val = None
|
||||
for v in values:
|
||||
if v['id'] == i:
|
||||
val = v[name]
|
||||
break
|
||||
def to_field_args(self):
|
||||
args = super(binary, self).to_field_args()
|
||||
args['attachment'] = self.attachment
|
||||
return args
|
||||
|
||||
# If client is requesting only the size of the field, we return it instead
|
||||
# of the content. Presumably a separate request will be done to read the actual
|
||||
# content if it's needed at some point.
|
||||
# TODO: after 6.0 we should consider returning a dict with size and content instead of
|
||||
# having an implicit convention for the value
|
||||
if val and context.get('bin_size_%s' % name, context.get('bin_size')):
|
||||
res[i] = tools.human_size(long(val))
|
||||
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
|
||||
if self.attachment:
|
||||
# values are stored in attachments, retrieve them
|
||||
atts = obj.pool['ir.attachment'].browse(cr, SUPERUSER_ID, [], context)
|
||||
domain = [
|
||||
('res_model', '=', obj._name),
|
||||
('res_field', '=', name),
|
||||
('res_id', 'in', ids),
|
||||
]
|
||||
for att in atts.search(domain):
|
||||
# the 'bin_size' flag is handled by the field 'datas' itself
|
||||
result[att.res_id] = att.datas
|
||||
else:
|
||||
# If client is requesting only the size of the field, we return it
|
||||
# instead of the content. Presumably a separate request will be done
|
||||
# to read the actual content if it's needed at some point.
|
||||
context = context or {}
|
||||
if context.get('bin_size') or context.get('bin_size_%s' % name):
|
||||
postprocess = lambda val: tools.human_size(long(val))
|
||||
else:
|
||||
res[i] = val
|
||||
return res
|
||||
postprocess = lambda val: val
|
||||
for val in (values or []):
|
||||
result[val['id']] = postprocess(val[name])
|
||||
|
||||
return result
|
||||
|
||||
def set(self, cr, obj, id, name, value, user=None, context=None):
|
||||
assert self.attachment
|
||||
# retrieve the attachment that stores the value, and adapt it
|
||||
att = obj.pool['ir.attachment'].browse(cr, SUPERUSER_ID, [], context).search([
|
||||
('res_model', '=', obj._name),
|
||||
('res_field', '=', name),
|
||||
('res_id', '=', id),
|
||||
])
|
||||
if value:
|
||||
if att:
|
||||
att.write({'datas': value})
|
||||
else:
|
||||
att.create({
|
||||
'name': name,
|
||||
'res_model': obj._name,
|
||||
'res_field': name,
|
||||
'res_id': id,
|
||||
'type': 'binary',
|
||||
'datas': value,
|
||||
})
|
||||
else:
|
||||
att.unlink()
|
||||
return []
|
||||
|
||||
class selection(_column):
|
||||
_type = 'selection'
|
||||
@@ -1397,6 +1432,9 @@ class function(_column):
|
||||
args['store'] = bool(self.store)
|
||||
if self._type in ('float',):
|
||||
args['digits'] = self._digits_compute or self._digits
|
||||
elif self._type in ('binary',):
|
||||
# limitation: binary function fields cannot be stored in attachments
|
||||
args['attachment'] = False
|
||||
elif self._type in ('selection', 'reference'):
|
||||
args['selection'] = self.selection
|
||||
elif self._type in ('many2one', 'one2many', 'many2many'):
|
||||
|
||||
Reference in New Issue
Block a user