-
+
+
diff --git a/doc/cla/corporate/ITLibertas.md b/doc/cla/corporate/ITLibertas.md
new file mode 100644
index 00000000000..06626c6d5ad
--- /dev/null
+++ b/doc/cla/corporate/ITLibertas.md
@@ -0,0 +1,17 @@
+Russia, 2015-08-14
+
+IT Libertas agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+IT Libertas LLC info@itlibertas.com https://github.com/itlibertas
+
+List of contributors:
+
+Denis Baranov baranov@itlibertas.com https://github.com/barachka
+Tatiana Deribina deribina@itlibertas.com https://github.com/Sapfiriana
+Aleksandr Shevelev shevelev@itlibertas.com https://github.com/alexalv
diff --git a/doc/cla/individual/flh.md b/doc/cla/individual/flh.md
new file mode 100644
index 00000000000..90860f3e0c4
--- /dev/null
+++ b/doc/cla/individual/flh.md
@@ -0,0 +1,11 @@
+France, 2015-08-18
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Florian Hatat github@hatat.me https://github.com/flh
diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py
index 577bed483be..a30f0da1850 100644
--- a/openerp/addons/base/ir/ir_http.py
+++ b/openerp/addons/base/ir/ir_http.py
@@ -107,11 +107,9 @@ class ir_http(osv.AbstractModel):
datas = attach[0]['datas'] or ''
name = attach[0]['name']
- if not datas:
- if name.startswith(('http://', 'https://', '/')):
- return werkzeug.utils.redirect(name, 301)
- else:
- return werkzeug.wrappers.Response(status=204) # NO CONTENT
+ if (not datas and name != request.httprequest.path and
+ name.startswith(('http://', 'https://', '/'))):
+ return werkzeug.utils.redirect(name, 301)
response = werkzeug.wrappers.Response()
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
diff --git a/openerp/addons/base/rng/view.rng b/openerp/addons/base/rng/view.rng
index 3eee1860024..f2bcd74bc8a 100644
--- a/openerp/addons/base/rng/view.rng
+++ b/openerp/addons/base/rng/view.rng
@@ -259,6 +259,11 @@
+
+
+
+
+
diff --git a/openerp/addons/base/tests/test_search.py b/openerp/addons/base/tests/test_search.py
index bd9d5089356..6cb8490546d 100644
--- a/openerp/addons/base/tests/test_search.py
+++ b/openerp/addons/base/tests/test_search.py
@@ -5,6 +5,18 @@ import openerp.tests.common as common
class test_search(common.TransactionCase):
+ def patch_order(self, model, order):
+ m_e = self.env[model]
+ m_r = self.registry(model)
+
+ old_order = m_e._order
+
+ @self.addCleanup
+ def cleanup():
+ m_r._order = type(m_e)._order = old_order
+
+ m_r._order = type(m_e)._order = order
+
def test_00_search_order(self):
registry, cr, uid = self.registry, self.cr, self.uid
@@ -96,17 +108,13 @@ class test_search(common.TransactionCase):
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
# Do: order on many2one, but not by specifying in order parameter of search, but by overriding _order of res_users
- users_class = type(users_obj)
- users_order = users_class._order
- users_class._order = 'country_id desc, name asc, login desc'
- self.addCleanup(setattr, users_class, '_order', users_order)
-
+ self.patch_order('res.users', 'country_id desc, name asc, login desc')
user_ids = users_obj.search(cr, search_user, [])
expected_ids = [search_user, c, b, a]
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
- def test_11_indirect_inherits_m2order(self):
+ def test_11_indirect_inherits_m2o_order(self):
registry, cr, uid = self.registry, self.cr, self.uid
Cron = registry('ir.cron')
Users = registry('res.users')
@@ -121,6 +129,48 @@ class test_search(common.TransactionCase):
expected_ids = [cron_ids[l] for l in 'ABC']
self.assertEqual(ids, expected_ids)
+ def test_12_m2o_order_loop_self(self):
+ registry, cr, uid = self.registry, self.cr, self.uid
+
+ Cats = registry('ir.module.category')
+ ids = {}
+ def create(name, **kw):
+ ids[name] = Cats.create(cr, uid, dict(kw, name=name))
+
+ self.patch_order('ir.module.category', 'parent_id desc, name')
+
+ create('A')
+ create('B', parent_id=ids['A'])
+ create('C', parent_id=ids['A'])
+ create('D')
+ create('E', parent_id=ids['D'])
+ create('F', parent_id=ids['D'])
+
+ expected_order = [ids[x] for x in 'ADEFBC']
+ domain = [('id', 'in', ids.values())]
+ search_result = Cats.search(cr, uid, domain)
+ self.assertEqual(search_result, expected_order)
+
+ def test_13_m2o_order_loop_multi(self):
+ Users = self.env['res.users']
+
+ # will sort by login desc of the creator, then by name
+ self.patch_order('res.partner', 'create_uid, name')
+ self.patch_order('res.users', 'partner_id, login desc')
+
+ kw = dict(groups_id=[(6, 0, [self.ref('base.group_system'),
+ self.ref('base.group_partner_manager')])])
+
+ u1 = Users.create(dict(name='Q', login='m', **kw)).id
+ u2 = Users.sudo(user=u1).create(dict(name='B', login='f', **kw)).id
+ u3 = Users.create(dict(name='C', login='c', **kw)).id
+ u4 = Users.sudo(user=u2).create(dict(name='D', login='z', **kw)).id
+
+ expected_order = [u2, u4, u3, u1]
+
+ domain = [('id', 'in', [u1, u2, u3, u4])]
+ search_result = list(Users.search(domain)._ids)
+ self.assertEqual(search_result, expected_order)
if __name__ == '__main__':
unittest2.main()
diff --git a/openerp/models.py b/openerp/models.py
index 93cc537d12f..6aaecba62be 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -4541,7 +4541,7 @@ class BaseModel(object):
return '"%s"."%s"' % (table_alias, field)
@api.model
- def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction):
+ def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction, seen):
"""
Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
either native m2o fields or function/related fields that are stored, including
@@ -4551,18 +4551,18 @@ class BaseModel(object):
"""
if order_field not in self._columns and order_field in self._inherit_fields:
# also add missing joins for reaching the table containing the m2o field
- qualified_field = self._inherits_join_calc(alias, order_field, query)
order_field_column = self._inherit_fields[order_field][2]
+ qualified_field = self._inherits_join_calc(alias, order_field, query)
+ alias, order_field = qualified_field.replace('"', '').split('.', 1)
else:
- qualified_field = '"%s"."%s"' % (alias, order_field)
order_field_column = self._columns[order_field]
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
- _logger.debug("Many2one function/related fields must be stored " \
- "to be used as ordering fields! Ignoring sorting for %s.%s",
- self._name, order_field)
- return
+ _logger.debug("Many2one function/related fields must be stored "
+ "to be used as ordering fields! Ignoring sorting for %s.%s",
+ self._name, order_field)
+ return []
# figure out the applicable order_by for the m2o
dest_model = self.env[order_field_column._obj]
@@ -4573,13 +4573,15 @@ class BaseModel(object):
# Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
# as we don't want to exclude results that have NULL values for the m2o
- src_table, src_field = qualified_field.replace('"', '').split('.', 1)
- dst_alias, dst_alias_statement = query.add_join((src_table, dest_model._table, src_field, 'id', src_field), implicit=False, outer=True)
+ join = (alias, dest_model._table, order_field, 'id', order_field)
+ dst_alias, dst_alias_statement = query.add_join(join, implicit=False, outer=True)
return dest_model._generate_order_by_inner(dst_alias, m2o_order, query,
- reverse_direction=reverse_direction)
+ reverse_direction=reverse_direction, seen=seen)
@api.model
- def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False):
+ def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False, seen=None):
+ if seen is None:
+ seen = set()
order_by_elements = []
self._check_qorder(order_spec)
for order_part in order_spec.split(','):
@@ -4603,7 +4605,10 @@ class BaseModel(object):
inner_clauses = ['"%s"."%s"' % (alias, order_field)]
add_dir = True
elif order_column._type == 'many2one':
- inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ key = (self._name, order_column._obj, order_field)
+ if key not in seen:
+ seen.add(key)
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse, seen)
else:
continue # ignore non-readable or "non-joinable" fields
elif order_field in self._inherit_fields:
@@ -4613,7 +4618,10 @@ class BaseModel(object):
inner_clauses = [self._inherits_join_calc(alias, order_field, query)]
add_dir = True
elif order_column._type == 'many2one':
- inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ key = (parent_obj._name, order_column._obj, order_field)
+ if key not in seen:
+ seen.add(key)
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse, seen)
else:
continue # ignore non-readable or "non-joinable" fields
else: