diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index 4a4d77142f4..4c917cc6729 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -96,7 +96,11 @@ class CompanyLDAP(osv.osv): return False entry = False - filter = filter_format(conf['ldap_filter'], (login,)) + try: + filter = filter_format(conf['ldap_filter'], (login,)) + except TypeError: + _logger.warning('Could not format LDAP filter. Your filter should contain one \'%s\'.') + return False try: results = self.query(conf, filter) diff --git a/addons/mail/mail_template.py b/addons/mail/mail_template.py index 61581475f93..1209d2c38bb 100644 --- a/addons/mail/mail_template.py +++ b/addons/mail/mail_template.py @@ -43,9 +43,18 @@ def format_tz(pool, cr, uid, dt, tz=False, format=False, context=None): if tz: context['tz'] = tz or pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz'] or "UTC" timestamp = datetime.datetime.strptime(dt, tools.DEFAULT_SERVER_DATETIME_FORMAT) - ts = fields.datetime.context_timestamp(cr, uid, timestamp, context) + # Babel allows to format datetime in a specific language without change locale + # So month 1 = January in English, and janvier in French + # Be aware that the default value for format is 'medium', instead of 'short' + # medium: Jan 5, 2016, 10:20:31 PM | 5 janv. 2016 22:20:31 + # short: 1/5/16, 10:20 PM | 5/01/16 22:20 + if context.get('use_babel'): + # Formatting available here : http://babel.pocoo.org/en/latest/dates.html#date-fields + from babel.dates import format_datetime + return format_datetime(ts, format or 'medium', locale=context.get("lang") or 'en_US') + if format: return ts.strftime(format) else: diff --git a/addons/mrp/views/report_mrporder.xml b/addons/mrp/views/report_mrporder.xml index c94189769bc..3c905717b82 100644 --- a/addons/mrp/views/report_mrporder.xml +++ b/addons/mrp/views/report_mrporder.xml @@ -106,7 +106,7 @@ Consumed Products - > + diff --git a/addons/product/product.py b/addons/product/product.py index 641ebc0081c..3ec3ba6266c 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -774,7 +774,7 @@ class product_template(osv.osv): ctx.update(active_test=False) product_ids = [] for product in self.browse(cr, uid, ids, context=ctx): - product_ids = map(int,product.product_variant_ids) + product_ids += map(int, product.product_variant_ids) self.pool.get("product.product").write(cr, uid, product_ids, {'active': vals.get('active')}, context=ctx) return res diff --git a/openerp/addons/test_access_rights/ir.model.access.csv b/openerp/addons/test_access_rights/ir.model.access.csv index a0bd5facb02..eda40f960d9 100644 --- a/openerp/addons/test_access_rights/ir.model.access.csv +++ b/openerp/addons/test_access_rights/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_test_access_right_some_obj,access_test_access_right_some_obj,model_test_access_right_some_obj,,1,1,1,1 +access_test_access_right_container,access_test_access_right_container,model_test_access_right_container,,1,1,1,1 diff --git a/openerp/addons/test_access_rights/models.py b/openerp/addons/test_access_rights/models.py index b7752cb3665..0b0d056b597 100644 --- a/openerp/addons/test_access_rights/models.py +++ b/openerp/addons/test_access_rights/models.py @@ -4,3 +4,9 @@ class SomeObj(models.Model): _name = 'test_access_right.some_obj' val = fields.Integer() + + +class Container(models.Model): + _name = 'test_access_right.container' + + some_ids = fields.Many2many('test_access_right.some_obj', 'test_access_right_rel', 'container_id', 'some_id') diff --git a/openerp/addons/test_access_rights/tests/test_ir_rules.py b/openerp/addons/test_access_rights/tests/test_ir_rules.py index 6cfb1bf9de7..81eb328ef3d 100644 --- a/openerp/addons/test_access_rights/tests/test_ir_rules.py +++ b/openerp/addons/test_access_rights/tests/test_ir_rules.py @@ -31,3 +31,25 @@ class TestRules(TransactionCase): # but this should with self.assertRaises(openerp.exceptions.AccessError): self.assertEqual(browse2.val, -1) + + def test_many2many(self): + """ Test assignment of many2many field where rules apply. """ + ids = [self.id1, self.id2] + + # create container as superuser, connected to all some_objs + container_admin = self.env['test_access_right.container'].create({'some_ids': [(6, 0, ids)]}) + self.assertItemsEqual(container_admin.some_ids.ids, ids) + + # check the container as the public user + container_user = container_admin.sudo(self.browse_ref('base.public_user')) + self.assertItemsEqual(container_user.some_ids.ids, [self.id1]) + + # this should not fail + container_user.write({'some_ids': [(6, 0, ids)]}) + self.assertItemsEqual(container_user.some_ids.ids, [self.id1]) + self.assertItemsEqual(container_admin.some_ids.ids, ids) + + # this removes accessible records only + container_user.write({'some_ids': [(5,)]}) + self.assertItemsEqual(container_user.some_ids.ids, []) + self.assertItemsEqual(container_admin.some_ids.ids, [self.id2]) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 61d6114dcb3..902daa8f865 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1007,6 +1007,25 @@ class many2many(_column): return rel, id1, id2 = self._sql_names(model) obj = model.pool[self._obj] + + def link(ids): + # beware of duplicates when inserting + query = """ INSERT INTO {rel} ({id1}, {id2}) + (SELECT %s, unnest(%s)) EXCEPT (SELECT {id1}, {id2} FROM {rel} WHERE {id1}=%s) + """.format(rel=rel, id1=id1, id2=id2) + for sub_ids in cr.split_for_in_conditions(ids): + cr.execute(query, (id, list(sub_ids), id)) + + def unlink_all(): + # remove all records for which user has access rights + clauses, params, tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context) + cond = " AND ".join(clauses) if clauses else "1=1" + query = """ DELETE FROM {rel} USING {tables} + WHERE {rel}.{id1}=%s AND {rel}.{id2}={table}.id AND {cond} + """.format(rel=rel, id1=id1, id2=id2, + table=obj._table, tables=','.join(tables), cond=cond) + cr.execute(query, [id] + params) + for act in values: if not (isinstance(act, list) or isinstance(act, tuple)) or not act: continue @@ -1020,23 +1039,12 @@ class many2many(_column): elif act[0] == 3: cr.execute('delete from '+rel+' where ' + id1 + '=%s and '+ id2 + '=%s', (id, act[1])) elif act[0] == 4: - # following queries are in the same transaction - so should be relatively safe - cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+' = %s and '+id2+' = %s', (id, act[1])) - if not cr.fetchone(): - cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s,%s)', (id, act[1])) + link([act[1]]) elif act[0] == 5: - cr.execute('delete from '+rel+' where ' + id1 + ' = %s', (id,)) + unlink_all() elif act[0] == 6: - - d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context) - if d1: - d1 = ' and ' + ' and '.join(d1) - else: - d1 = '' - cr.execute('delete from '+rel+' where '+id1+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2) - - for act_nbr in act[2]: - cr.execute('insert into '+rel+' ('+id1+','+id2+') values (%s, %s)', (id, act_nbr)) + unlink_all() + link(act[2]) # # TODO: use a name_search