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 @@
|
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
|