[MERGE] forward port of branch saas-3 up to revid 5088 chs@openerp.com-20140311095550-lg3nvvjyojvgp2po
bzr revid: chs@openerp.com-20140311133850-11bw9vv90e40clw1
This commit is contained in:
commit
96f744b271
|
@ -77,7 +77,9 @@ class ir_http(osv.AbstractModel):
|
||||||
# what if error in security.check()
|
# what if error in security.check()
|
||||||
# -> res_users.check()
|
# -> res_users.check()
|
||||||
# -> res_users.check_credentials()
|
# -> res_users.check_credentials()
|
||||||
except Exception:
|
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
||||||
|
# All other exceptions mean undetermined status (e.g. connection pool full),
|
||||||
|
# let them bubble up
|
||||||
request.session.logout()
|
request.session.logout()
|
||||||
getattr(self, "_auth_method_%s" % auth_method)()
|
getattr(self, "_auth_method_%s" % auth_method)()
|
||||||
return auth_method
|
return auth_method
|
||||||
|
|
|
@ -1117,6 +1117,10 @@ class ir_model_data(osv.osv):
|
||||||
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
|
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
|
||||||
# has been turned off on the model.
|
# has been turned off on the model.
|
||||||
field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
|
field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
|
||||||
|
if not field.exists():
|
||||||
|
_logger.info('Deleting orphan external_ids %s', external_ids)
|
||||||
|
self.unlink(cr, uid, external_ids)
|
||||||
|
continue
|
||||||
if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
|
if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
|
||||||
continue
|
continue
|
||||||
if field.name == 'id':
|
if field.name == 'id':
|
||||||
|
|
|
@ -32,7 +32,7 @@ class QWebException(Exception):
|
||||||
class QWebTemplateNotFound(QWebException):
|
class QWebTemplateNotFound(QWebException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def convert_to_qweb_exception(etype=None, **kw):
|
def raise_qweb_exception(etype=None, **kw):
|
||||||
if etype is None:
|
if etype is None:
|
||||||
etype = QWebException
|
etype = QWebException
|
||||||
orig_type, original, tb = sys.exc_info()
|
orig_type, original, tb = sys.exc_info()
|
||||||
|
@ -43,7 +43,7 @@ def convert_to_qweb_exception(etype=None, **kw):
|
||||||
e.qweb[k] = v
|
e.qweb[k] = v
|
||||||
# Will use `raise foo from bar` in python 3 and rename cause to __cause__
|
# Will use `raise foo from bar` in python 3 and rename cause to __cause__
|
||||||
e.qweb['cause'] = original
|
e.qweb['cause'] = original
|
||||||
return e
|
raise
|
||||||
|
|
||||||
class QWebContext(dict):
|
class QWebContext(dict):
|
||||||
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
|
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
|
||||||
|
@ -166,7 +166,7 @@ class QWeb(orm.AbstractModel):
|
||||||
try:
|
try:
|
||||||
xml_doc = qwebcontext.loader(name)
|
xml_doc = qwebcontext.loader(name)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise convert_to_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
|
raise_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
|
||||||
self.load_document(xml_doc, qwebcontext=qwebcontext)
|
self.load_document(xml_doc, qwebcontext=qwebcontext)
|
||||||
|
|
||||||
if name in qwebcontext.templates:
|
if name in qwebcontext.templates:
|
||||||
|
@ -179,7 +179,7 @@ class QWeb(orm.AbstractModel):
|
||||||
return qwebcontext.safe_eval(expr)
|
return qwebcontext.safe_eval(expr)
|
||||||
except Exception:
|
except Exception:
|
||||||
template = qwebcontext.get('__template__')
|
template = qwebcontext.get('__template__')
|
||||||
raise convert_to_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
|
raise_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
|
||||||
|
|
||||||
def eval_object(self, expr, qwebcontext):
|
def eval_object(self, expr, qwebcontext):
|
||||||
return self.eval(expr, qwebcontext)
|
return self.eval(expr, qwebcontext)
|
||||||
|
@ -207,7 +207,7 @@ class QWeb(orm.AbstractModel):
|
||||||
return str(expr % qwebcontext)
|
return str(expr % qwebcontext)
|
||||||
except Exception:
|
except Exception:
|
||||||
template = qwebcontext.get('__template__')
|
template = qwebcontext.get('__template__')
|
||||||
raise convert_to_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
|
raise_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
|
||||||
|
|
||||||
def eval_bool(self, expr, qwebcontext):
|
def eval_bool(self, expr, qwebcontext):
|
||||||
return int(bool(self.eval(expr, qwebcontext)))
|
return int(bool(self.eval(expr, qwebcontext)))
|
||||||
|
@ -292,7 +292,7 @@ class QWeb(orm.AbstractModel):
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
template = qwebcontext.get('__template__')
|
template = qwebcontext.get('__template__')
|
||||||
raise convert_to_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
|
raise_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
|
||||||
name = str(element.nodeName)
|
name = str(element.nodeName)
|
||||||
inner = "".join(g_inner)
|
inner = "".join(g_inner)
|
||||||
trim = template_attributes.get("trim", 0)
|
trim = template_attributes.get("trim", 0)
|
||||||
|
@ -611,6 +611,9 @@ class DateTimeConverter(osv.AbstractModel):
|
||||||
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
||||||
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
||||||
|
|
||||||
|
if options and options.get('hide_seconds'):
|
||||||
|
pattern = pattern.replace(":ss", "").replace(":s", "")
|
||||||
|
|
||||||
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
||||||
|
|
||||||
class TextConverter(osv.AbstractModel):
|
class TextConverter(osv.AbstractModel):
|
||||||
|
|
|
@ -728,9 +728,26 @@ class view(osv.osv):
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
self.read_template.clear_cache(self)
|
self.read_template.clear_cache(self)
|
||||||
|
|
||||||
|
def _contains_branded(self, node):
|
||||||
|
return node.tag == 't'\
|
||||||
|
or 't-raw' in node.attrib\
|
||||||
|
or any(self.is_node_branded(child) for child in node.iterdescendants())
|
||||||
|
|
||||||
|
def _pop_view_branding(self, element):
|
||||||
|
distributed_branding = dict(
|
||||||
|
(attribute, element.attrib.pop(attribute))
|
||||||
|
for attribute in MOVABLE_BRANDING
|
||||||
|
if element.get(attribute))
|
||||||
|
return distributed_branding
|
||||||
|
|
||||||
def distribute_branding(self, e, branding=None, parent_xpath='',
|
def distribute_branding(self, e, branding=None, parent_xpath='',
|
||||||
index_map=misc.ConstantMapping(1)):
|
index_map=misc.ConstantMapping(1)):
|
||||||
if e.get('t-ignore') or e.tag == 'head':
|
if e.get('t-ignore') or e.tag == 'head':
|
||||||
|
# remove any view branding possibly injected by inheritance
|
||||||
|
attrs = set(MOVABLE_BRANDING)
|
||||||
|
for descendant in e.iterdescendants(tag=etree.Element):
|
||||||
|
if not attrs.intersection(descendant.attrib): continue
|
||||||
|
self._pop_view_branding(descendant)
|
||||||
# TODO: find a better name and check if we have a string to boolean helper
|
# TODO: find a better name and check if we have a string to boolean helper
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -742,15 +759,15 @@ class view(osv.osv):
|
||||||
e.set('data-oe-xpath', node_path)
|
e.set('data-oe-xpath', node_path)
|
||||||
if not e.get('data-oe-model'): return
|
if not e.get('data-oe-model'): return
|
||||||
|
|
||||||
# if a branded element contains branded elements distribute own
|
if set(('t-esc', 't-escf', 't-raw', 't-rawf')).intersection(e.attrib):
|
||||||
# branding to children unless it's t-raw, then just remove branding
|
# nodes which fully generate their content and have no reason to
|
||||||
# on current element
|
# be branded because they can not sensibly be edited
|
||||||
if e.tag == 't' or 't-raw' in e.attrib or \
|
self._pop_view_branding(e)
|
||||||
any(self.is_node_branded(child) for child in e.iterdescendants()):
|
elif self._contains_branded(e):
|
||||||
distributed_branding = dict(
|
# if a branded element contains branded elements distribute own
|
||||||
(attribute, e.attrib.pop(attribute))
|
# branding to children unless it's t-raw, then just remove branding
|
||||||
for attribute in MOVABLE_BRANDING
|
# on current element
|
||||||
if e.get(attribute))
|
distributed_branding = self._pop_view_branding(e)
|
||||||
|
|
||||||
if 't-raw' not in e.attrib:
|
if 't-raw' not in e.attrib:
|
||||||
# TODO: collections.Counter if remove p2.6 compat
|
# TODO: collections.Counter if remove p2.6 compat
|
||||||
|
@ -760,11 +777,12 @@ class view(osv.osv):
|
||||||
if child.get('data-oe-xpath'):
|
if child.get('data-oe-xpath'):
|
||||||
# injected by view inheritance, skip otherwise
|
# injected by view inheritance, skip otherwise
|
||||||
# generated xpath is incorrect
|
# generated xpath is incorrect
|
||||||
continue
|
self.distribute_branding(child)
|
||||||
indexes[child.tag] += 1
|
else:
|
||||||
self.distribute_branding(child, distributed_branding,
|
indexes[child.tag] += 1
|
||||||
parent_xpath=node_path,
|
self.distribute_branding(
|
||||||
index_map=indexes)
|
child, distributed_branding,
|
||||||
|
parent_xpath=node_path, index_map=indexes)
|
||||||
|
|
||||||
def is_node_branded(self, node):
|
def is_node_branded(self, node):
|
||||||
""" Finds out whether a node is branded or qweb-active (bears a
|
""" Finds out whether a node is branded or qweb-active (bears a
|
||||||
|
|
|
@ -208,20 +208,19 @@ class res_company(osv.osv):
|
||||||
res['value'] = {'currency_id': currency_id}
|
res['value'] = {'currency_id': currency_id}
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _search(self, cr, uid, args, offset=0, limit=None, order=None,
|
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
|
||||||
context=None, count=False, access_rights_uid=None):
|
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
if context.get('user_preference'):
|
if context.pop('user_preference', None):
|
||||||
# We browse as superuser. Otherwise, the user would be able to
|
# We browse as superuser. Otherwise, the user would be able to
|
||||||
# select only the currently visible companies (according to rules,
|
# select only the currently visible companies (according to rules,
|
||||||
# which are probably to allow to see the child companies) even if
|
# which are probably to allow to see the child companies) even if
|
||||||
# she belongs to some other companies.
|
# she belongs to some other companies.
|
||||||
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
|
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
|
||||||
cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids]))
|
cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids]))
|
||||||
return cmp_ids
|
uid = SUPERUSER_ID
|
||||||
return super(res_company, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
args = (args or []) + [('id', 'in', cmp_ids)]
|
||||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
return super(res_company, self).name_search(cr, uid, name=name, args=args, operator=operator, context=context, limit=limit)
|
||||||
|
|
||||||
def _company_default_get(self, cr, uid, object=False, field=False, context=None):
|
def _company_default_get(self, cr, uid, object=False, field=False, context=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -350,6 +350,7 @@ class res_partner(osv.osv, format_address):
|
||||||
def copy(self, cr, uid, id, default=None, context=None):
|
def copy(self, cr, uid, id, default=None, context=None):
|
||||||
if default is None:
|
if default is None:
|
||||||
default = {}
|
default = {}
|
||||||
|
default['user_ids'] = False
|
||||||
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
|
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
|
||||||
default.update({'name': _('%s (copy)') % name})
|
default.update({'name': _('%s (copy)') % name})
|
||||||
return super(res_partner, self).copy(cr, uid, id, default, context)
|
return super(res_partner, self).copy(cr, uid, id, default, context)
|
||||||
|
|
|
@ -282,7 +282,7 @@
|
||||||
<group name="preferences" col="4">
|
<group name="preferences" col="4">
|
||||||
<field name="lang" readonly="0"/>
|
<field name="lang" readonly="0"/>
|
||||||
<field name="tz" readonly="0"/>
|
<field name="tz" readonly="0"/>
|
||||||
<field name="company_id" widget="selection" readonly="0"
|
<field name="company_id" options="{'no_create': True}" readonly="0"
|
||||||
groups="base.group_multi_company"/>
|
groups="base.group_multi_company"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Email Preferences">
|
<group string="Email Preferences">
|
||||||
|
|
|
@ -179,3 +179,51 @@ class TestPropertyField(common.TransactionCase):
|
||||||
self.partner.write(cr, alice, [partner_id], {'property_country': country_be})
|
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, 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")
|
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 = """<p>Oops this should maybe be sanitized
|
||||||
|
% if object.some_field and not object.oriented:
|
||||||
|
<table>
|
||||||
|
% if object.other_field:
|
||||||
|
<tr>
|
||||||
|
${object.mako_thing}
|
||||||
|
<td>
|
||||||
|
</tr>
|
||||||
|
% endif
|
||||||
|
<tr>
|
||||||
|
%if object.dummy_field:
|
||||||
|
<p>Youpie</p>
|
||||||
|
%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('</table>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
|
||||||
|
self.assertIn('</td>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
|
||||||
|
|
||||||
|
self.partner._columns = old_columns
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
from lxml.builder import E
|
from lxml.builder import E
|
||||||
|
|
||||||
|
@ -8,6 +10,24 @@ from openerp.tests import common
|
||||||
|
|
||||||
Field = E.field
|
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):
|
class TestNodeLocator(common.BaseCase):
|
||||||
"""
|
"""
|
||||||
The node locator returns None when it can not find a node, and the first
|
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'))
|
E.foo(attr='1', version='3'))
|
||||||
self.assertIsNone(node)
|
self.assertIsNone(node)
|
||||||
|
|
||||||
class TestViewInheritance(common.TransactionCase):
|
class TestViewInheritance(ViewCase):
|
||||||
def arch_for(self, name, view_type='form', parent=None):
|
def arch_for(self, name, view_type='form', parent=None):
|
||||||
""" Generates a trivial view of the specified ``view_type``.
|
""" Generates a trivial view of the specified ``view_type``.
|
||||||
|
|
||||||
|
@ -206,7 +226,7 @@ class TestViewInheritance(common.TransactionCase):
|
||||||
self.View.default_view(
|
self.View.default_view(
|
||||||
self.cr, self.uid, model=self.model, view_type='graph'))
|
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
|
""" Applies a sequence of inheritance specification nodes to a base
|
||||||
architecture. IO state parameters (cr, uid, model, context) are used for
|
architecture. IO state parameters (cr, uid, model, context) are used for
|
||||||
error reporting
|
error reporting
|
||||||
|
@ -230,8 +250,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(Field(name="replacement"), string="Title")))
|
E.form(Field(name="replacement"), string="Title"))
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
spec = Field(name="target", position="replace")
|
spec = Field(name="target", position="replace")
|
||||||
|
@ -241,8 +261,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(string="Title")))
|
E.form(string="Title"))
|
||||||
|
|
||||||
def test_insert_after(self):
|
def test_insert_after(self):
|
||||||
spec = Field(
|
spec = Field(
|
||||||
|
@ -254,12 +274,12 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(
|
E.form(
|
||||||
Field(name="target"),
|
Field(name="target"),
|
||||||
Field(name="inserted"),
|
Field(name="inserted"),
|
||||||
string="Title"
|
string="Title"
|
||||||
)))
|
))
|
||||||
|
|
||||||
def test_insert_before(self):
|
def test_insert_before(self):
|
||||||
spec = Field(
|
spec = Field(
|
||||||
|
@ -271,11 +291,11 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(
|
E.form(
|
||||||
Field(name="inserted"),
|
Field(name="inserted"),
|
||||||
Field(name="target"),
|
Field(name="target"),
|
||||||
string="Title")))
|
string="Title"))
|
||||||
|
|
||||||
def test_insert_inside(self):
|
def test_insert_inside(self):
|
||||||
default = Field(Field(name="inserted"), name="target")
|
default = Field(Field(name="inserted"), name="target")
|
||||||
|
@ -289,13 +309,13 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(
|
E.form(
|
||||||
Field(
|
Field(
|
||||||
Field(name="inserted"),
|
Field(name="inserted"),
|
||||||
Field(name="inserted 2"),
|
Field(name="inserted 2"),
|
||||||
name="target"),
|
name="target"),
|
||||||
string="Title")))
|
string="Title"))
|
||||||
|
|
||||||
def test_unpack_data(self):
|
def test_unpack_data(self):
|
||||||
spec = E.data(
|
spec = E.data(
|
||||||
|
@ -310,15 +330,15 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(self.base_arch),
|
self.base_arch,
|
||||||
ET.tostring(E.form(
|
E.form(
|
||||||
Field(
|
Field(
|
||||||
Field(name="inserted 0"),
|
Field(name="inserted 0"),
|
||||||
Field(name="inserted 1"),
|
Field(name="inserted 1"),
|
||||||
Field(name="inserted 2"),
|
Field(name="inserted 2"),
|
||||||
Field(name="inserted 3"),
|
Field(name="inserted 3"),
|
||||||
name="target"),
|
name="target"),
|
||||||
string="Title")))
|
string="Title"))
|
||||||
|
|
||||||
def test_invalid_position(self):
|
def test_invalid_position(self):
|
||||||
spec = Field(
|
spec = Field(
|
||||||
|
@ -350,18 +370,18 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||||
self.base_arch,
|
self.base_arch,
|
||||||
spec, None)
|
spec, None)
|
||||||
|
|
||||||
class TestApplyInheritedArchs(common.TransactionCase):
|
class TestApplyInheritedArchs(ViewCase):
|
||||||
""" Applies a sequence of modificator archs to a base view
|
""" Applies a sequence of modificator archs to a base view
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class TestViewCombined(common.TransactionCase):
|
class TestViewCombined(ViewCase):
|
||||||
"""
|
"""
|
||||||
Test fallback operations of View.read_combined:
|
Test fallback operations of View.read_combined:
|
||||||
* defaults mapping
|
* defaults mapping
|
||||||
* ?
|
* ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class TestNoModel(common.TransactionCase):
|
class TestNoModel(ViewCase):
|
||||||
def test_create_view_nomodel(self):
|
def test_create_view_nomodel(self):
|
||||||
View = self.registry('ir.ui.view')
|
View = self.registry('ir.ui.view')
|
||||||
view_id = View.create(self.cr, self.uid, {
|
view_id = View.create(self.cr, self.uid, {
|
||||||
|
@ -411,13 +431,11 @@ class TestNoModel(common.TransactionCase):
|
||||||
'value': translated_text,
|
'value': translated_text,
|
||||||
})
|
})
|
||||||
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
|
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):
|
def setUp(self):
|
||||||
import openerp.modules
|
import openerp.modules
|
||||||
super(TestTemplating, self).setUp()
|
super(TestTemplating, self).setUp()
|
||||||
|
@ -473,7 +491,126 @@ class TestTemplating(common.TransactionCase):
|
||||||
second.get('data-oe-id'),
|
second.get('data-oe-id'),
|
||||||
"second should come from the extension view")
|
"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': """<root>
|
||||||
|
<item order="1"/>
|
||||||
|
</root>"""
|
||||||
|
})
|
||||||
|
id2 = Views.create(self.cr, self.uid, {
|
||||||
|
'name': "Extension",
|
||||||
|
'type': 'qweb',
|
||||||
|
'inherit_id': id,
|
||||||
|
'arch': """<xpath expr="//item" position="before">
|
||||||
|
<item order="2">
|
||||||
|
<content t-att-href="foo">bar</content>
|
||||||
|
</item>
|
||||||
|
</xpath>"""
|
||||||
|
})
|
||||||
|
|
||||||
|
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': """<root>
|
||||||
|
<item><span t-esc="foo"/></item>
|
||||||
|
</root>""",
|
||||||
|
})
|
||||||
|
|
||||||
|
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': """<root>
|
||||||
|
<item order="1" t-ignore="true">
|
||||||
|
<t t-esc="foo"/>
|
||||||
|
</item>
|
||||||
|
</root>"""
|
||||||
|
})
|
||||||
|
id2 = Views.create(self.cr, self.uid, {
|
||||||
|
'name': "Extension",
|
||||||
|
'type': 'qweb',
|
||||||
|
'inherit_id': id,
|
||||||
|
'arch': """<xpath expr="//item[@order='1']" position="inside">
|
||||||
|
<item order="2">
|
||||||
|
<content t-att-href="foo">bar</content>
|
||||||
|
</item>
|
||||||
|
</xpath>"""
|
||||||
|
})
|
||||||
|
|
||||||
|
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):
|
def test_nonexistent_attribute_removal(self):
|
||||||
Views = self.registry('ir.ui.view')
|
Views = self.registry('ir.ui.view')
|
||||||
|
@ -589,16 +726,17 @@ class test_views(common.TransactionCase):
|
||||||
})
|
})
|
||||||
self.assertEqual(view['type'], 'form')
|
self.assertEqual(view['type'], 'form')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(ET.fromstring(
|
ET.fromstring(
|
||||||
view['arch'],
|
view['arch'],
|
||||||
parser=ET.XMLParser(remove_blank_text=True)
|
parser=ET.XMLParser(remove_blank_text=True)
|
||||||
)),
|
),
|
||||||
'<form string="Replacement title" version="7.0">'
|
E.form(
|
||||||
'<p>Replacement data</p>'
|
E.p("Replacement data"),
|
||||||
'<footer thing="bob">'
|
E.footer(
|
||||||
'<button name="action_next" type="object" string="New button"/>'
|
E.button(name="action_next", type="object", string="New button"),
|
||||||
'</footer>'
|
thing="bob"
|
||||||
'</form>')
|
),
|
||||||
|
string="Replacement title", version="7.0"))
|
||||||
|
|
||||||
def test_view_inheritance_divergent_models(self):
|
def test_view_inheritance_divergent_models(self):
|
||||||
Views = self.registry('ir.ui.view')
|
Views = self.registry('ir.ui.view')
|
||||||
|
@ -656,14 +794,13 @@ class test_views(common.TransactionCase):
|
||||||
})
|
})
|
||||||
self.assertEqual(view['type'], 'form')
|
self.assertEqual(view['type'], 'form')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ET.tostring(ET.fromstring(
|
ET.fromstring(
|
||||||
view['arch'],
|
view['arch'],
|
||||||
parser=ET.XMLParser(remove_blank_text=True)
|
parser=ET.XMLParser(remove_blank_text=True)
|
||||||
)),
|
),
|
||||||
'<form string="Replacement title" version="7.0">'
|
E.form(
|
||||||
'<p>Replacement data</p>'
|
E.p("Replacement data"),
|
||||||
'<footer>'
|
E.footer(
|
||||||
'<button name="action_next" type="object" string="New button"/>'
|
E.button(name="action_next", type="object", string="New button")),
|
||||||
'</footer>'
|
string="Replacement title", version="7.0"
|
||||||
'</form>')
|
))
|
||||||
|
|
||||||
|
|
|
@ -209,20 +209,19 @@ class WebRequest(object):
|
||||||
# Backward for 7.0
|
# Backward for 7.0
|
||||||
if self.endpoint.first_arg_is_req:
|
if self.endpoint.first_arg_is_req:
|
||||||
args = (request,) + args
|
args = (request,) + args
|
||||||
|
|
||||||
# Correct exception handling and concurency retry
|
# Correct exception handling and concurency retry
|
||||||
@service_model.check
|
@service_model.check
|
||||||
def checked_call(___dbname, *a, **kw):
|
def checked_call(___dbname, *a, **kw):
|
||||||
return self.endpoint(*a, **kw)
|
# 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.
|
||||||
# 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:
|
|
||||||
if self._cr:
|
if self._cr:
|
||||||
self._cr.rollback()
|
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
|
@property
|
||||||
def debug(self):
|
def debug(self):
|
||||||
|
@ -733,7 +732,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
|
||||||
self.setdefault("uid", None)
|
self.setdefault("uid", None)
|
||||||
self.setdefault("login", None)
|
self.setdefault("login", None)
|
||||||
self.setdefault("password", None)
|
self.setdefault("password", None)
|
||||||
self.setdefault("context", {'tz': "UTC", "uid": None})
|
self.setdefault("context", {})
|
||||||
|
|
||||||
def get_context(self):
|
def get_context(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -236,15 +236,24 @@ class char(_column):
|
||||||
class text(_column):
|
class text(_column):
|
||||||
_type = 'text'
|
_type = 'text'
|
||||||
|
|
||||||
|
|
||||||
class html(text):
|
class html(text):
|
||||||
_type = 'html'
|
_type = 'html'
|
||||||
_symbol_c = '%s'
|
_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 None
|
||||||
return html_sanitize(x)
|
if not self._sanitize:
|
||||||
|
return value
|
||||||
_symbol_set = (_symbol_c, _symbol_f)
|
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__
|
import __builtin__
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue