diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 804fdb218bc..6f0435735dd 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -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()!) diff --git a/openerp/addons/base/ir/ir_attachment_view.xml b/openerp/addons/base/ir/ir_attachment_view.xml index 35b3f806695..616faec3889 100644 --- a/openerp/addons/base/ir/ir_attachment_view.xml +++ b/openerp/addons/base/ir/ir_attachment_view.xml @@ -21,6 +21,7 @@ + @@ -51,6 +52,7 @@ + diff --git a/openerp/fields.py b/openerp/fields.py index 66cc64f7349..42c20951ab5 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -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): diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 0a474115d20..9d70d1e44a5 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -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'):