1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from osv import fields,osv
25 from osv.orm import except_orm
28 class ir_attachment(osv.osv):
29 def check(self, cr, uid, ids, mode, context=None, values=None):
30 """Restricts the access to an ir.attachment, according to referred model
31 In the 'document' module, it is overriden to relax this hard rule, since
32 more complex ones apply there.
36 ima = self.pool.get('ir.model.access')
39 if isinstance(ids, (int, long)):
41 cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,))
42 for rmod, rid in cr.fetchall():
43 if not (rmod and rid):
45 res_ids.setdefault(rmod,set()).add(rid)
47 if 'res_model' in values and 'res_id' in values:
48 res_ids.setdefault(values['res_model'],set()).add(values['res_id'])
50 for model, mids in res_ids.items():
51 # ignore attachments that are not attached to a resource anymore when checking access rights
52 # (resource was deleted but attachment was not)
53 cr.execute('select id from '+self.pool.get(model)._table+' where id in %s', (tuple(mids),))
54 mids = [x[0] for x in cr.fetchall()]
55 ima.check(cr, uid, model, mode, context=context)
56 self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
58 def search(self, cr, uid, args, offset=0, limit=None, order=None,
59 context=None, count=False):
60 ids = super(ir_attachment, self).search(cr, uid, args, offset=offset,
61 limit=limit, order=order,
62 context=context, count=False)
68 # Work with a set, as list.remove() is prohibitive for large lists of documents
69 # (takes 20+ seconds on a db with 100k docs during search_count()!)
73 # For attachments, the permissions of the document they are attached to
74 # apply, so we must remove attachments for which the user cannot access
75 # the linked document.
76 # Use pure SQL rather than read() as it is about 50% faster for large dbs (100k+ docs),
77 # and the permissions are checked in super() and below anyway.
78 cr.execute("""SELECT id, res_model, res_id FROM ir_attachment WHERE id = ANY(%s)""", (list(ids),))
79 targets = cr.dictfetchall()
80 model_attachments = {}
81 for target_dict in targets:
82 if not (target_dict['res_id'] and target_dict['res_model']):
84 # model_attachments = { 'model': { 'res_id': [id1,id2] } }
85 model_attachments.setdefault(target_dict['res_model'],{}).setdefault(target_dict['res_id'],set()).add(target_dict['id'])
87 # To avoid multiple queries for each attachment found, checks are
88 # performed in batch as much as possible.
89 ima = self.pool.get('ir.model.access')
90 for model, targets in model_attachments.iteritems():
91 if not ima.check(cr, uid, model, 'read', raise_exception=False, context=context):
92 # remove all corresponding attachment ids
93 for attach_id in itertools.chain(*targets.values()):
95 continue # skip ir.rule processing, these ones are out already
97 # filter ids according to what access rules permit
98 target_ids = targets.keys()
99 allowed_ids = self.pool.get(model).search(cr, uid, [('id', 'in', target_ids)], context=context)
100 disallowed_ids = set(target_ids).difference(allowed_ids)
101 for res_id in disallowed_ids:
102 for attach_id in targets[res_id]:
103 ids.remove(attach_id)
105 # sort result according to the original sort ordering
106 result = [id for id in orig_ids if id in ids]
107 return len(result) if count else list(result)
109 def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
110 self.check(cr, uid, ids, 'read', context=context)
111 return super(ir_attachment, self).read(cr, uid, ids, fields_to_read, context, load)
113 def write(self, cr, uid, ids, vals, context=None):
114 self.check(cr, uid, ids, 'write', context=context, values=vals)
115 return super(ir_attachment, self).write(cr, uid, ids, vals, context)
117 def copy(self, cr, uid, id, default=None, context=None):
118 self.check(cr, uid, [id], 'write', context=context)
119 return super(ir_attachment, self).copy(cr, uid, id, default, context)
121 def unlink(self, cr, uid, ids, context=None):
122 self.check(cr, uid, ids, 'unlink', context=context)
123 return super(ir_attachment, self).unlink(cr, uid, ids, context)
125 def create(self, cr, uid, values, context=None):
126 self.check(cr, uid, [], mode='create', context=context, values=values)
127 return super(ir_attachment, self).create(cr, uid, values, context)
129 def action_get(self, cr, uid, context=None):
130 return self.pool.get('ir.actions.act_window').for_xml_id(
131 cr, uid, 'base', 'action_attachment', context=context)
133 def _name_get_resname(self, cr, uid, ids, object, method, context):
135 for attachment in self.browse(cr, uid, ids, context=context):
136 model_object = attachment.res_model
137 res_id = attachment.res_id
138 if model_object and res_id:
139 model_pool = self.pool.get(model_object)
140 res = model_pool.name_get(cr,uid,[res_id],context)
141 res_name = res and res[0][1] or False
143 field = self._columns.get('res_name',False)
144 if field and len(res_name) > field.size:
145 res_name = res_name[:field.size-3] + '...'
146 data[attachment.id] = res_name
148 data[attachment.id] = False
151 _name = 'ir.attachment'
153 'name': fields.char('Attachment Name',size=256, required=True),
154 'datas': fields.binary('Data'),
155 'datas_fname': fields.char('Filename',size=256),
156 'description': fields.text('Description'),
157 'res_name': fields.function(_name_get_resname, type='char', size=128,
158 string='Resource Name', method=True, store=True),
159 'res_model': fields.char('Resource Object',size=64, readonly=True,
160 help="The database object this attachment will be attached to"),
161 'res_id': fields.integer('Resource ID', readonly=True,
162 help="The record id this is attached to"),
163 'url': fields.char('Url', size=512, oldname="link"),
164 'type': fields.selection(
165 [ ('url','URL'), ('binary','Binary'), ],
166 'Type', help="Binary File or external URL", required=True, change_default=True),
168 'create_date': fields.datetime('Date Created', readonly=True),
169 'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
170 'company_id': fields.many2one('res.company', 'Company', change_default=True),
175 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
178 def _auto_init(self, cr, context=None):
179 super(ir_attachment, self)._auto_init(cr, context)
180 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
181 if not cr.fetchone():
182 cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
188 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: