',
'An error occured in a modal and I will send you back the html to try opening one on your end']
REMOVE_CLASS_OUT = [
'
',
diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py
index c331f426748..7a8f921af55 100644
--- a/odoo/addons/base/tests/test_qweb.py
+++ b/odoo/addons/base/tests/test_qweb.py
@@ -14,10 +14,6 @@ from odoo.addons.base.ir.ir_qweb import QWebException
from odoo.tools import pycompat, misc, ustr
-def dedent_and_strip(string):
- return ''.join([line.strip() for line in string.splitlines()])
-
-
class TestQWebTField(TransactionCase):
def setUp(self):
super(TestQWebTField, self).setUp()
@@ -30,13 +26,13 @@ class TestQWebTField(TransactionCase):
result = self.engine.render(field, {'company': company})
self.assertEqual(
- result,
- '%s' % (
+ etree.fromstring(result),
+ etree.fromstring(u'%s' % (
company.id,
- "My Test Company",
- ),
+ u"My Test Company",
+ )),
)
def test_i18n(self):
@@ -46,13 +42,13 @@ class TestQWebTField(TransactionCase):
result = self.engine.render(field, {'company': company})
self.assertEqual(
- ustr(result),
- '%s' % (
+ etree.fromstring(result),
+ etree.fromstring(u'%s' % (
company.id,
misc.html_escape(s),
- ),
+ )),
)
def test_reject_crummy_tags(self):
@@ -72,7 +68,7 @@ class TestQWebNS(TransactionCase):
def test_render_static_xml_with_namespace(self):
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
"""
- expected_result = """
+ expected_result = u"""
@@ -89,17 +85,17 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
%s
""" % expected_result
})
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), etree.fromstring(expected_result))
def test_render_static_xml_with_namespace_2(self):
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
"""
- expected_result = """
+ expected_result = u"""
Book Review
@@ -127,12 +123,12 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
%s
""" % expected_result
})
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), etree.fromstring(expected_result))
def test_render_static_xml_with_useless_distributed_namespace(self):
""" Test that redundant namespaces are stripped upon rendering.
@@ -140,7 +136,7 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -154,7 +150,7 @@ class TestQWebNS(TransactionCase):
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
@@ -163,24 +159,24 @@ class TestQWebNS(TransactionCase):
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_static_xml_with_namespace_3(self):
- expected_result = """
+ expected_result = u"""
"""
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
%s
""" % expected_result
})
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), etree.fromstring(expected_result))
def test_render_dynamic_xml_with_namespace_t_esc(self):
""" Test that rendering a template containing a node having both an ns declaration and a t-esc attribute correctly
@@ -189,16 +185,16 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
"""
})
- expected_result = """test"""
+ expected_result = etree.fromstring(u"""test""")
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_dynamic_xml_with_namespace_t_esc_with_useless_distributed_namespace(self):
""" Test that rendering a template containing a node having both an ns declaration and a t-esc attribute correctly
@@ -207,7 +203,7 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
blabla
@@ -216,13 +212,13 @@ class TestQWebNS(TransactionCase):
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
blabla
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_dynamic_xml_with_namespace_t_attf(self):
""" Test that rendering a template containing a node having both an ns declaration and a t-attf attribute correctly
@@ -231,7 +227,7 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -248,7 +244,7 @@ class TestQWebNS(TransactionCase):
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
@@ -260,9 +256,9 @@ class TestQWebNS(TransactionCase):
80
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_dynamic_xml_with_namespace_t_attf_with_useless_distributed_namespace(self):
""" Test that rendering a template containing a node having both an ns declaration and a t-attf attribute correctly
@@ -271,7 +267,7 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -289,7 +285,7 @@ class TestQWebNS(TransactionCase):
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
@@ -302,15 +298,15 @@ class TestQWebNS(TransactionCase):
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_dynamic_xml_with_namespace_2(self):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -322,7 +318,7 @@ class TestQWebNS(TransactionCase):
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
1.0
@@ -335,45 +331,45 @@ class TestQWebNS(TransactionCase):
Oasis 4
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.render(dict(version_id=1.0))), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render({'version_id': 1.0})), expected_result)
def test_render_static_xml_with_namespaced_attributes(self):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
abc
"""
})
- expected_result = """abc"""
+ expected_result = etree.fromstring(u"""abc""")
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_dynamic_xml_with_namespaced_attributes(self):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
"""
})
- expected_result = """abc"""
+ expected_result = etree.fromstring("""abc""")
- self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
+ self.assertEqual(etree.fromstring(view1.render()), expected_result)
def test_render_static_xml_with_t_call(self):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -386,13 +382,13 @@ class TestQWebNS(TransactionCase):
"""
})
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
- "VALUES ('dummy', 'ir.ui.view', %s, 'base')" % view1.id)
+ "VALUES ('dummy', 'ir.ui.view', %s, 'base')", [view1.id])
# view2 will t-call view1
view2 = self.env['ir.ui.view'].create({
'name': "dummy2",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -406,19 +402,19 @@ class TestQWebNS(TransactionCase):
# check that the root tag has all its xmlns
expected_ns = {
- None: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
- 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
- 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
+ (None, 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'),
+ ('cac', 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'),
+ ('cbc', 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'),
}
- self.assertSetEqual(set(pycompat.items(expected_ns)) - set(pycompat.items(result_etree.nsmap)), set())
+ self.assertEqual(set(pycompat.items(result_etree.nsmap)), expected_ns)
# check that the t-call did its work
cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'})
- self.assertEquals(len(cac_lines), 2)
- self.assertEquals(result.count('Appel'), 2)
+ self.assertEqual(len(cac_lines), 2)
+ self.assertEqual(result.count(b'Appel'), 2)
# check that the t-call dit not output again the xmlns declaration
- self.assertEquals(result.count('xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"'), 1)
+ self.assertEqual(result.count(b'xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"'), 1)
def test_render_static_xml_with_extension(self):
""" Test the extension of a view by an xpath expression on a ns prefixed element.
@@ -427,7 +423,7 @@ class TestQWebNS(TransactionCase):
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
- 'arch': """
+ 'arch': u"""
@@ -445,14 +441,14 @@ class TestQWebNS(TransactionCase):
'name': "dummy_ext",
'type': 'qweb',
'inherit_id': view1.id,
- 'arch': """
+ 'arch': u"""
Oranges
"""
})
- expected_result = """
+ expected_result = etree.fromstring(u"""
@@ -462,9 +458,12 @@ class TestQWebNS(TransactionCase):
- """
+ """)
- self.assertEquals(dedent_and_strip(view1.with_context(check_view_ids=[view1.id, view2.id]).render()), dedent_and_strip(expected_result))
+ self.assertEqual(
+ etree.fromstring(view1.with_context(check_view_ids=[view1.id, view2.id]).render()),
+ expected_result
+ )
from copy import deepcopy
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index b9b8cd0fe39..3f007b5f71b 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -2,10 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import partial
-try:
- from itertools import zip_longest as izip_longuest
-except ImportError:
- from itertools import izip_longest
from lxml import etree
from lxml.builder import E
@@ -28,22 +24,8 @@ class ViewXMLID(common.TransactionCase):
class ViewCase(common.TransactionCase):
def setUp(self):
super(ViewCase, self).setUp()
- self.addTypeEqualityFunc(etree._Element, self.assertTreesEqual)
self.View = self.env['ir.ui.view']
- def assertTreesEqual(self, n1, n2, msg=None):
- self.assertEqual(n1.tag, n2.tag, msg)
- self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
- self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
-
- # Because lxml uses ordereddicts in which order is important to
- # equality (!?!?!?!)
- self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
-
- for c1, c2 in izip_longest(n1, n2):
- self.assertEqual(c1, c2, msg)
-
-
class TestNodeLocator(common.TransactionCase):
"""
The node locator returns None when it can not find a node, and the first
diff --git a/odoo/fields.py b/odoo/fields.py
index fec4666e925..37c57dcd5c4 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -7,7 +7,9 @@ from collections import OrderedDict, defaultdict
from datetime import date, datetime
from functools import partial
from operator import attrgetter
+import itertools
import logging
+
import pytz
try:
from xmlrpc.client import MAXINT
@@ -119,7 +121,7 @@ class MetaField(type):
elif attr.startswith('_description_'):
cls.description_attrs.append((attr[13:], attr))
-
+_global_seq = iter(itertools.count())
class Field(MetaField('DummyField', (object,), {})):
""" The field descriptor contains the field definition, and manages accesses
and assignments of the corresponding field on records. The following
@@ -296,6 +298,7 @@ class Field(MetaField('DummyField', (object,), {})):
'_attrs': EMPTY_DICT, # the field's non-slot attributes
'_module': None, # the field's module name
'_setup_done': None, # the field's setup state: None, 'base' or 'full'
+ '_sequence': None, # absolute ordering of the field
'automatic': False, # whether the field is automatically created ("magic" field)
'inherited': False, # whether the field is inherited (_inherits)
@@ -336,6 +339,7 @@ class Field(MetaField('DummyField', (object,), {})):
def __init__(self, string=Default, **kwargs):
kwargs['string'] = string
+ self._sequence = kwargs['_sequence'] = next(_global_seq)
args = {key: val for key, val in pycompat.items(kwargs) if val is not Default}
self.args = args or EMPTY_DICT
self._setup_done = None
diff --git a/odoo/models.py b/odoo/models.py
index 97fc5976572..71040650173 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -425,7 +425,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
'_original_module': cls._module,
'_inherit_children': OrderedSet(), # names of children models
'_inherits_children': set(), # names of children models
- '_fields': {}, # populated in _setup_base()
+ '_fields': OrderedDict(), # populated in _setup_base()
})
check_parent = cls._build_model_check_parent
@@ -2272,7 +2272,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# therefore two registries are never set up at the same time.
# remove fields that are not proper to cls
- for name in set(cls._fields) - cls0._proper_fields:
+ for name in OrderedSet(cls._fields) - cls0._proper_fields:
delattr(cls, name)
cls._fields.pop(name, None)
# collect proper fields on cls0, and add them on cls
@@ -2283,20 +2283,20 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
self._add_field(name, field)
else:
self._add_field(name, field.new(**field.args))
- cls._proper_fields = set(cls._fields)
+ cls._proper_fields = OrderedSet(cls._fields)
else:
# retrieve fields from parent classes, and duplicate them on cls to
# avoid clashes with inheritance between different models
for name in cls._fields:
delattr(cls, name)
- cls._fields = {}
- for name, field in getmembers(cls, Field.__instancecheck__):
+ cls._fields = OrderedDict()
+ for name, field in sorted(getmembers(cls, Field.__instancecheck__), key=lambda f: f[1]._sequence):
# do not retrieve magic, custom and inherited fields
if not any(field.args.get(k) for k in ('automatic', 'manual', 'inherited')):
self._add_field(name, field.new())
self._add_magic_fields()
- cls._proper_fields = set(cls._fields)
+ cls._proper_fields = OrderedSet(cls._fields)
cls.pool.model_cache[cls._model_cache_key] = cls
@@ -3423,7 +3423,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
if not key.startswith('default_')}
# call the 'write' method of fields which are not columns
- for name in upd_todo:
+ for name in sorted(upd_todo, key=lambda name: self._fields[name]._sequence):
field = self._fields[name]
field.write(self.with_context(rel_context), vals[name])
diff --git a/odoo/osv/orm.py b/odoo/osv/orm.py
index 368e60cd255..e4be2706c7f 100644
--- a/odoo/osv/orm.py
+++ b/odoo/osv/orm.py
@@ -129,32 +129,30 @@ def test_modifiers(what, expected):
node = etree.fromstring(what)
transfer_node_to_modifiers(node, modifiers)
simplify_modifiers(modifiers)
- dump = json.dumps(modifiers)
- assert dump == expected, "%s != %s" % (dump, expected)
+ assert modifiers == expected, "%s != %s" % (modifiers, expected)
elif isinstance(what, dict):
transfer_field_to_modifiers(what, modifiers)
simplify_modifiers(modifiers)
- dump = json.dumps(modifiers)
- assert dump == expected, "%s != %s" % (dump, expected)
+ assert modifiers == expected, "%s != %s" % (modifiers, expected)
# To use this test:
# import odoo
# odoo.osv.orm.modifiers_tests()
def modifiers_tests():
- test_modifiers('', '{}')
- test_modifiers('', '{"invisible": true}')
- test_modifiers('', '{"readonly": true}')
- test_modifiers('', '{"required": true}')
- test_modifiers('', '{}')
- test_modifiers('', '{}')
- test_modifiers('', '{}')
- test_modifiers('', '{"invisible": true, "required": true}') # TODO order is not guaranteed
- test_modifiers('', '{"invisible": true}')
- test_modifiers('', '{"required": true}')
- test_modifiers("""""", '{"invisible": [["b", "=", "c"]]}')
+ test_modifiers('', {})
+ test_modifiers('', {"invisible": True})
+ test_modifiers('', {"readonly": True})
+ test_modifiers('', {"required": True})
+ test_modifiers('', {})
+ test_modifiers('', {})
+ test_modifiers('', {})
+ test_modifiers('', {"invisible": True, "required": True}) # TODO order is not guaranteed
+ test_modifiers('', {"invisible": True})
+ test_modifiers('', {"required": True})
+ test_modifiers("""""", {"invisible": [["b", "=", "c"]]})
# The dictionary is supposed to be the result of fields_get().
- test_modifiers({}, '{}')
- test_modifiers({"invisible": True}, '{"invisible": true}')
- test_modifiers({"invisible": False}, '{}')
+ test_modifiers({}, {})
+ test_modifiers({"invisible": True}, {"invisible": True})
+ test_modifiers({"invisible": False}, {})
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index 823a56c6190..b0f601f284a 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -18,10 +18,15 @@ import itertools
import unittest
from contextlib import contextmanager
from datetime import datetime, timedelta
+from lxml import etree
from pprint import pformat
import requests
+try:
+ from itertools import zip_longest as izip_longest
+except ImportError:
+ from itertools import izip_longest
try:
from xmlrpc import client as xmlrpclib
except ImportError:
@@ -82,7 +87,24 @@ def post_install(flag):
return obj
return decorator
-class BaseCase(unittest.TestCase):
+class TreeCase(unittest.TestCase):
+ def __init__(self, methodName='runTest'):
+ super(TreeCase, self).__init__(methodName)
+ self.addTypeEqualityFunc(etree._Element, self.assertTreesEqual)
+
+ def assertTreesEqual(self, n1, n2, msg=None):
+ self.assertEqual(n1.tag, n2.tag, msg)
+ # Because lxml.attrib is an ordereddict for which order is important
+ # to equality, even though *we* don't care
+ self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
+
+ self.assertEqual((n1.text or u'').strip(), (n2.text or u'').strip(), msg)
+ self.assertEqual((n1.tail or u'').strip(), (n2.tail or u'').strip(), msg)
+
+ for c1, c2 in izip_longest(n1, n2):
+ self.assertEqual(c1, c2, msg)
+
+class BaseCase(TreeCase):
"""
Subclass of TestCase for common OpenERP-specific code.
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index 7fd6ca833bb..530f3e63862 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import collections
import logging
from lxml.html import clean
import random
@@ -147,7 +148,7 @@ class _Cleaner(clean.Cleaner):
attributes = el.attrib
styling = attributes.get('style')
if styling:
- valid_styles = {}
+ valid_styles = collections.OrderedDict()
styles = self._style_re.findall(styling)
for style in styles:
if style[0].lower() in self._style_whitelist: