diff --git a/openerp/addons/base/tests/test_fields.py b/openerp/addons/base/tests/test_fields.py
index 47ffcd2f5b1..ce34bceb014 100644
--- a/openerp/addons/base/tests/test_fields.py
+++ b/openerp/addons/base/tests/test_fields.py
@@ -179,3 +179,51 @@ class TestPropertyField(common.TransactionCase):
self.partner.write(cr, alice, [partner_id], {'property_country': country_be})
self.assertEqual(self.partner.browse(cr, alice, partner_id).property_country.id, country_be, "Alice does not see the value he has set on the property field")
self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Changes made by Alice have overwritten Bob's value")
+
+
+class TestHtmlField(common.TransactionCase):
+
+ def setUp(self):
+ super(TestHtmlField, self).setUp()
+ self.partner = self.registry('res.partner')
+
+ def test_00_sanitize(self):
+ cr, uid, context = self.cr, self.uid, {}
+ old_columns = self.partner._columns
+ self.partner._columns = dict(old_columns)
+ self.partner._columns.update({
+ 'comment': fields.html('Secure Html', sanitize=False),
+ })
+ some_ugly_html = """Oops this should maybe be sanitized
+% if object.some_field and not object.oriented:
+
+ % if object.other_field:
+
+ ${object.mako_thing}
+
+ |
+ % endif
+
+%if object.dummy_field:
+ Youpie
+%endif"""
+
+ pid = self.partner.create(cr, uid, {
+ 'name': 'Raoul Poilvache',
+ 'comment': some_ugly_html,
+ }, context=context)
+ partner = self.partner.browse(cr, uid, pid, context=context)
+ self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')
+
+ self.partner._columns.update({
+ 'comment': fields.html('Unsecure Html', sanitize=True),
+ })
+ self.partner.write(cr, uid, [pid], {
+ 'comment': some_ugly_html,
+ }, context=context)
+ partner = self.partner.browse(cr, uid, pid, context=context)
+ # sanitize should have closed tags left open in the original html
+ self.assertIn('
', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
+ self.assertIn('', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
+
+ self.partner._columns = old_columns
diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py
index 6780996ed6e..06030a84792 100644
--- a/openerp/addons/base/tests/test_views.py
+++ b/openerp/addons/base/tests/test_views.py
@@ -1,6 +1,8 @@
# -*- encoding: utf-8 -*-
from functools import partial
+import unittest2
+
from lxml import etree as ET
from lxml.builder import E
@@ -8,6 +10,24 @@ from openerp.tests import common
Field = E.field
+class ViewCase(common.TransactionCase):
+ def setUp(self):
+ super(ViewCase, self).setUp()
+ self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
+
+ def assertTreesEqual(self, n1, n2, msg=None):
+ self.assertEqual(n1.tag, n2.tag)
+ 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 zip(n1, n2):
+ self.assertTreesEqual(c1, c2, msg)
+
+
class TestNodeLocator(common.BaseCase):
"""
The node locator returns None when it can not find a node, and the first
@@ -98,7 +118,7 @@ class TestNodeLocator(common.BaseCase):
E.foo(attr='1', version='3'))
self.assertIsNone(node)
-class TestViewInheritance(common.TransactionCase):
+class TestViewInheritance(ViewCase):
def arch_for(self, name, view_type='form', parent=None):
""" Generates a trivial view of the specified ``view_type``.
@@ -206,7 +226,7 @@ class TestViewInheritance(common.TransactionCase):
self.View.default_view(
self.cr, self.uid, model=self.model, view_type='graph'))
-class TestApplyInheritanceSpecs(common.TransactionCase):
+class TestApplyInheritanceSpecs(ViewCase):
""" Applies a sequence of inheritance specification nodes to a base
architecture. IO state parameters (cr, uid, model, context) are used for
error reporting
@@ -230,8 +250,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(Field(name="replacement"), string="Title")))
+ self.base_arch,
+ E.form(Field(name="replacement"), string="Title"))
def test_delete(self):
spec = Field(name="target", position="replace")
@@ -241,8 +261,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(string="Title")))
+ self.base_arch,
+ E.form(string="Title"))
def test_insert_after(self):
spec = Field(
@@ -254,12 +274,12 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(name="target"),
Field(name="inserted"),
string="Title"
- )))
+ ))
def test_insert_before(self):
spec = Field(
@@ -271,11 +291,11 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(name="inserted"),
Field(name="target"),
- string="Title")))
+ string="Title"))
def test_insert_inside(self):
default = Field(Field(name="inserted"), name="target")
@@ -289,13 +309,13 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(
Field(name="inserted"),
Field(name="inserted 2"),
name="target"),
- string="Title")))
+ string="Title"))
def test_unpack_data(self):
spec = E.data(
@@ -310,15 +330,15 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
- ET.tostring(self.base_arch),
- ET.tostring(E.form(
+ self.base_arch,
+ E.form(
Field(
Field(name="inserted 0"),
Field(name="inserted 1"),
Field(name="inserted 2"),
Field(name="inserted 3"),
name="target"),
- string="Title")))
+ string="Title"))
def test_invalid_position(self):
spec = Field(
@@ -350,18 +370,18 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
self.base_arch,
spec, None)
-class TestApplyInheritedArchs(common.TransactionCase):
+class TestApplyInheritedArchs(ViewCase):
""" Applies a sequence of modificator archs to a base view
"""
-class TestViewCombined(common.TransactionCase):
+class TestViewCombined(ViewCase):
"""
Test fallback operations of View.read_combined:
* defaults mapping
* ?
"""
-class TestNoModel(common.TransactionCase):
+class TestNoModel(ViewCase):
def test_create_view_nomodel(self):
View = self.registry('ir.ui.view')
view_id = View.create(self.cr, self.uid, {
@@ -411,13 +431,11 @@ class TestNoModel(common.TransactionCase):
'value': translated_text,
})
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
-
- self.text_para.text = translated_text
- self.assertEqual(
- ET.tostring(sarch, encoding='utf-8'),
- ET.tostring(self.arch, encoding='utf-8'))
-class TestTemplating(common.TransactionCase):
+ self.text_para.text = translated_text
+ self.assertEqual(sarch, self.arch)
+
+class TestTemplating(ViewCase):
def setUp(self):
import openerp.modules
super(TestTemplating, self).setUp()
@@ -473,7 +491,126 @@ class TestTemplating(common.TransactionCase):
second.get('data-oe-id'),
"second should come from the extension view")
-class test_views(common.TransactionCase):
+ def test_branding_distribute_inner(self):
+ """ Checks that the branding is correctly distributed within a view
+ extension
+ """
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+
+ """
+ })
+ id2 = Views.create(self.cr, self.uid, {
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': id,
+ 'arch': """
+ -
+ bar
+
+ """
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(
+ arch,
+ E.root(
+ E.item(
+ E.content("bar", {
+ 't-att-href': "foo",
+ 'data-oe-model': 'ir.ui.view',
+ 'data-oe-id': str(id2),
+ 'data-oe-field': 'arch',
+ 'data-oe-xpath': '/xpath/item/content[1]',
+ }), {
+ 'order': '2',
+ 'data-oe-source-id': str(id)
+ }),
+ E.item({
+ 'order': '1',
+ 'data-oe-model': 'ir.ui.view',
+ 'data-oe-id': str(id),
+ 'data-oe-field': 'arch',
+ 'data-oe-xpath': '/root[1]/item[1]'
+ })
+ )
+ )
+
+ def test_esc_no_branding(self):
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base View",
+ 'type': 'qweb',
+ 'arch': """
+
+ """,
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(arch, E.root(E.item(E.span({'t-esc': "foo"}))))
+
+ def test_ignore_unbrand(self):
+ Views = self.registry('ir.ui.view')
+ id = Views.create(self.cr, self.uid, {
+ 'name': "Base view",
+ 'type': 'qweb',
+ 'arch': """
+ -
+
+
+ """
+ })
+ id2 = Views.create(self.cr, self.uid, {
+ 'name': "Extension",
+ 'type': 'qweb',
+ 'inherit_id': id,
+ 'arch': """
+ -
+ bar
+
+ """
+ })
+
+ arch_string = Views.read_combined(
+ self.cr, self.uid, id, fields=['arch'],
+ context={'inherit_branding': True})['arch']
+
+ arch = ET.fromstring(arch_string)
+ Views.distribute_branding(arch)
+
+ self.assertEqual(
+ arch,
+ E.root(
+ E.item(
+ {'t-ignore': 'true', 'order': '1'},
+ E.t({'t-esc': 'foo'}),
+ E.item(
+ {'order': '2', 'data-oe-source-id': str(id)},
+ E.content(
+ {'t-att-href': 'foo'},
+ "bar")
+ )
+ )
+ ),
+ "t-ignore should apply to injected sub-view branding, not just to"
+ " the main view's"
+ )
+
+class test_views(ViewCase):
def test_nonexistent_attribute_removal(self):
Views = self.registry('ir.ui.view')
@@ -589,16 +726,17 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
- ET.tostring(ET.fromstring(
+ ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
- )),
- '')
+ ),
+ E.form(
+ E.p("Replacement data"),
+ E.footer(
+ E.button(name="action_next", type="object", string="New button"),
+ thing="bob"
+ ),
+ string="Replacement title", version="7.0"))
def test_view_inheritance_divergent_models(self):
Views = self.registry('ir.ui.view')
@@ -656,14 +794,13 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
- ET.tostring(ET.fromstring(
+ ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
- )),
- '')
-
+ ),
+ E.form(
+ E.p("Replacement data"),
+ E.footer(
+ E.button(name="action_next", type="object", string="New button")),
+ string="Replacement title", version="7.0"
+ ))
diff --git a/openerp/http.py b/openerp/http.py
index 1750478a836..128d5a91246 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -209,20 +209,19 @@ class WebRequest(object):
# Backward for 7.0
if self.endpoint.first_arg_is_req:
args = (request,) + args
+
# Correct exception handling and concurency retry
@service_model.check
def checked_call(___dbname, *a, **kw):
- return self.endpoint(*a, **kw)
-
- # FIXME: code and rollback management could be cleaned
- try:
- if self.db:
- return checked_call(self.db, *args, **kwargs)
- return self.endpoint(*args, **kwargs)
- except Exception:
+ # The decorator can call us more than once if there is an database error. In this
+ # case, the request cursor is unusable. Rollback transaction to create a new one.
if self._cr:
self._cr.rollback()
- raise
+ return self.endpoint(*a, **kw)
+
+ if self.db:
+ return checked_call(self.db, *args, **kwargs)
+ return self.endpoint(*args, **kwargs)
@property
def debug(self):
@@ -733,7 +732,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.setdefault("uid", None)
self.setdefault("login", None)
self.setdefault("password", None)
- self.setdefault("context", {'tz': "UTC", "uid": None})
+ self.setdefault("context", {})
def get_context(self):
"""
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 478f982cd69..d5d0ee0fa30 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -236,15 +236,24 @@ class char(_column):
class text(_column):
_type = 'text'
+
class html(text):
_type = 'html'
_symbol_c = '%s'
- def _symbol_f(x):
- if x is None or x == False:
+
+ def _symbol_set_html(self, value):
+ if value is None or value is False:
return None
- return html_sanitize(x)
-
- _symbol_set = (_symbol_c, _symbol_f)
+ if not self._sanitize:
+ return value
+ return html_sanitize(value)
+
+ def __init__(self, string='unknown', sanitize=True, **args):
+ super(html, self).__init__(string=string, **args)
+ self._sanitize = sanitize
+ # symbol_set redefinition because of sanitize specific behavior
+ self._symbol_f = self._symbol_set_html
+ self._symbol_set = (self._symbol_c, self._symbol_f)
import __builtin__