diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index b7b71e1c9cd..ef7d3a9dc0f 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -18,6 +18,7 @@
# along with this program. If not, see .
#
##############################################################################
+import collections
import copy
import logging
import os
@@ -26,7 +27,8 @@ import re
import time
import HTMLParser
-from lxml import etree
+from lxml import etree, html
+from functools import partial
from openerp import tools
from openerp.osv import fields, osv, orm
@@ -252,24 +254,16 @@ class view(osv.osv):
return node
return None
- def inherit_branding(self, specs_tree, view_id, base_xpath=None, count=None):
- if not count:
- count = {}
- for node in specs_tree:
- try:
- count[node.tag] = count.get(node.tag, 0) + 1
- xpath = "%s/%s[%s]" % (base_xpath or '', node.tag, count.get(node.tag))
- if node.tag == 'data' or node.tag == 'xpath':
- node = self.inherit_branding(node, view_id, xpath, count)
- else:
- node.attrib.update({
- 'data-oe-model': 'ir.ui.view',
- 'data-oe-id': str(view_id),
- 'data-oe-field': 'arch',
- 'data-oe-xpath': xpath
- })
- except Exception,e:
- print "inherit branding error",e,xpath,node.tag
+ def inherit_branding(self, specs_tree, view_id):
+ for node in specs_tree.iterchildren(tag=etree.Element):
+ xpath = node.getroottree().getpath(node)
+ if node.tag == 'data' or node.tag == 'xpath':
+ self.inherit_branding(node, view_id)
+ else:
+ node.set('data-oe-id', str(view_id))
+ node.set('data-oe-xpath', xpath)
+ node.set('data-oe-model', 'ir.ui.view')
+ node.set('data-oe-field', 'arch')
return specs_tree
@@ -679,33 +673,58 @@ class view(osv.osv):
if 'lang' in context:
arch_tree = self.translate_qweb(cr, uid, id_, arch_tree, context['lang'], context)
self.distribute_branding(arch_tree)
- arch = etree.tostring(arch_tree, encoding='utf-8')
- arch = '%s' % (arch)
+ root = etree.Element('tpl')
+ root.append(arch_tree)
+ arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
return arch
- def distribute_branding(self, e, branding=None, xpath=None, count=None):
- if e.attrib.get('t-ignore') or e.tag in ('head',):
+ def distribute_branding(self, e, branding=None, parent_xpath='',
+ index_map=misc.ConstantMapping(1)):
+ if e.get('t-ignore') or e.tag == 'head':
# TODO: find a better name and check if we have a string to boolean helper
return
- branding_copy = ['data-oe-model','data-oe-id','data-oe-field','data-oe-xpath']
- branding_dist = {}
- xpath = "%s/%s[%s]" % (xpath or '', e.tag, (count and count.get(e.tag)) or 1)
- if branding and not (e.attrib.get('data-oe-model') or e.attrib.get('t-field')):
+
+ node_path = e.get('data-oe-xpath')
+ if node_path is None:
+ node_path = "%s/%s[%d]" % (parent_xpath, e.tag, index_map[e.tag])
+ if branding and not (e.get('data-oe-model') or e.get('t-field')):
e.attrib.update(branding)
- e.attrib['data-oe-xpath'] = xpath
- if e.attrib.get('data-oe-model'):
- # if a branded tag containg branded tag distribute to the childs
- child_text = "".join([etree.tostring(x, encoding='utf-8') for x in e])
- if re.search('( data-oe-model=| t-esc=| t-raw=| t-field=| t-call=| t-ignore=)',child_text) or e.tag == "t" or 't-raw' in e.attrib:
- for i in branding_copy:
- if e.attrib.get(i):
- branding_dist[i] = e.attrib.get(i)
- e.attrib.pop(i)
- if 't-raw' not in e.attrib:
- count = {}
- for child in e:
- count[child.tag] = count.get(child.tag, 0) + 1
- self.distribute_branding(child, branding_dist, xpath, count)
+ e.set('data-oe-xpath', node_path)
+ if not e.get('data-oe-model'): return
+
+ # if a branded element contains branded elements distribute own
+ # branding to children unless it's t-raw, then just remove branding
+ # on current element
+ if e.tag == 't' or 't-raw' in e.attrib or \
+ any(self.is_node_branded(child) for child in e.iterdescendants()):
+ distributed_branding = dict(
+ (attribute, e.attrib.pop(attribute))
+ for attribute in MOVABLE_BRANDING
+ if e.get(attribute))
+
+ if 't-raw' not in e.attrib:
+ # TODO: collections.Counter if remove p2.6 compat
+ # running index by tag type, for XPath query generation
+ indexes = collections.defaultdict(lambda: 0)
+ for child in e.iterchildren(tag=etree.Element):
+ indexes[child.tag] += 1
+ self.distribute_branding(child, distributed_branding,
+ parent_xpath=node_path,
+ index_map=indexes)
+
+ def is_node_branded(self, node):
+ """ Finds out whether a node is branded or qweb-active (bears a
+ @data-oe-model or a @t-* *which is not t-field* as t-field does not
+ section out views)
+
+ :param node: an etree-compatible element to test
+ :type node: etree._Element
+ :rtype: boolean
+ """
+ return any(
+ (attr == 'data-oe-model' or (attr != 't-field' and attr.startswith('t-')))
+ for attr in node.attrib
+ )
def translate_qweb(self, cr, uid, id_, arch, lang, context=None):
# TODO: this should be moved in a place before inheritance is applied
@@ -824,6 +843,7 @@ class view(osv.osv):
})
return super(view, self).copy(cr, uid, id, default, context=context)
+MOVABLE_BRANDING = ['data-oe-model','data-oe-id','data-oe-field','data-oe-xpath']
class view_sc(osv.osv):
_name = 'ir.ui.view_sc'
diff --git a/openerp/addons/base/res/res_partner_demo.xml b/openerp/addons/base/res/res_partner_demo.xml
index a8f0aa4eec7..a79623c5917 100644
--- a/openerp/addons/base/res/res_partner_demo.xml
+++ b/openerp/addons/base/res/res_partner_demo.xml
@@ -142,18 +142,18 @@
- Elec Import
+ OpenElec Applications
1
- Chicago
- 60623
+ 90001
+ Los Angeles
-
+
23 Rockwell Lane
- info@elecimport.com
- +1 773 439 3000
+ info@openelecapplications.com
+ +1 312 349 2121
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
diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py
index 6feba4df672..b83f824bfb0 100644
--- a/openerp/addons/base/tests/test_views.py
+++ b/openerp/addons/base/tests/test_views.py
@@ -3,7 +3,6 @@ from lxml import etree as ET
from lxml.builder import E
from openerp.tests import common
-import unittest2
Field = E.field
diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py
index 6df22828b84..0b65cfc63eb 100644
--- a/openerp/tools/convert.py
+++ b/openerp/tools/convert.py
@@ -49,7 +49,7 @@ except:
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
-from lxml import etree
+from lxml import etree, builder
import misc
from config import config
from translate import _
@@ -854,33 +854,33 @@ form: module.record_id""" % (xml_id,)
module, tpl_id = tpl_id.split('.', 1)
# set the full template name for qweb .
if not (el.get('inherit_id') or el.get('inherit_option_id')):
- el.attrib['t-name'] = '%s.%s' % (module, tpl_id)
+ el.set('t-name', '%s.%s' % (module, tpl_id))
el.tag = 't'
else:
el.tag = 'data'
el.attrib.pop('id', None)
- record = etree.Element('record')
record_attrs = {
'id': tpl_id,
'model': 'ir.ui.view',
}
for att in ['forcecreate', 'context']:
- if att in el.attrib:
+ if att in el.keys():
record_attrs[att] = el.attrib.pop(att)
- record.attrib.update(record_attrs)
+ Field = builder.E.field
name = el.get('name', tpl_id)
- record.append(etree.fromstring('%s' % name))
- record.append(etree.fromstring('qweb'))
- record.append(etree.fromstring(''))
- record[-1].append(el)
- for key in ('inherit_id','inherit_option_id'):
- if el.get(key):
- record.append(etree.fromstring('' % (key, el.get(key))))
- el.attrib.pop(key, None)
- if el.get('page'):
- record.append(etree.Element('field', name="page", eval="True"))
+
+ record = etree.Element('record', attrib=record_attrs)
+ record.append(Field(name, name='name'))
+ record.append(Field("qweb", name='type'))
+ record.append(Field(el, name="arch", type="xml"))
+ for field_name in ('inherit_id','inherit_option_id'):
+ value = el.attrib.pop(field_name, None)
+ if value: record.append(Field(name=field_name, ref=value))
+ if el.attrib.pop('page', None) == 'True':
+ record.append(Field(name="page", eval="True"))
+
return self._tag_record(cr, record, data_node)
def id_get(self, cr, id_str):
diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py
index 7895f2c7192..9efcb851cbf 100644
--- a/openerp/tools/mail.py
+++ b/openerp/tools/mail.py
@@ -99,7 +99,7 @@ def html_sanitize(src, silent=True):
# HTML Cleaner
#----------------------------------------------------------
-def html_email_clean(html, remove=False, shorten=False, max_length=300):
+def html_email_clean(html, remove=False, shorten=False, max_length=300, expand_options=None):
""" html_email_clean: clean the html by doing the following steps:
- try to strip email quotes, by removing blockquotes or having some client-
@@ -170,6 +170,9 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
iteration += 1
new_node = _insert_new_node(node, -1, new_node_tag, text[idx:] + (cur_node.tail or ''), None, {})
+ if expand_options is None:
+ expand_options = {}
+
if not html or not isinstance(html, basestring):
return html
html = ustr(html)
@@ -252,14 +255,26 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
stop_idx = len(outertext)
node.text = innertext + outertext[0:stop_idx]
# create ... read more node
- read_more_node = _create_node('span', ' ... ', None, {'class': 'oe_mail_expand'})
- read_more_link_node = _create_node('a', 'read more', None, {'href': '#', 'class': 'oe_mail_expand'})
+ read_more_node = _create_node(
+ 'span',
+ ' ... ',
+ None,
+ {'class': expand_options.get('oe_expand_span_class', 'oe_mail_expand')}
+ )
+ read_more_link_node = _create_node(
+ 'a',
+ 'read more',
+ None,
+ {
+ 'href': expand_options.get('oe_expand_href', '#'),
+ 'class': expand_options.get('oe_expand_a_class', 'oe_mail_expand'),
+ }
+ )
read_more_node.append(read_more_link_node)
# create outertext node
new_node = _create_node('span', outertext[stop_idx:])
# add newly created nodes in dom
- node.addnext(new_node)
- node.addnext(read_more_node)
+ node.append(read_more_node)
# tag node
new_node.set('in_overlength', '1')
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index 65e7c38e536..bdbb4d13e0f 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -35,7 +35,7 @@ import sys
import threading
import time
import zipfile
-from collections import defaultdict
+from collections import defaultdict, Mapping
from datetime import datetime
from itertools import islice, izip, groupby
from lxml import etree
@@ -1066,4 +1066,32 @@ def stripped_sys_argv(*strip_args):
return [x for i, x in enumerate(args) if not strip(args, i)]
+class ConstantMapping(Mapping):
+ """
+ An immutable mapping returning the provided value for every single key.
+
+ Useful for default value to methods
+ """
+ __slots__ = ['_value']
+ def __init__(self, val):
+ self._value = val
+
+ def __len__(self):
+ """
+ defaultdict updates its length for each individually requested key, is
+ that really useful?
+ """
+ return 0
+
+ def __iter__(self):
+ """
+ same as len, defaultdict udpates its iterable keyset with each key
+ requested, is there a point for this?
+ """
+ return iter([])
+
+ def __getitem__(self, item):
+ return self._value
+
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/tools/qweb.py b/openerp/tools/qweb.py
index 8c049edd56c..b3208d14168 100644
--- a/openerp/tools/qweb.py
+++ b/openerp/tools/qweb.py
@@ -1,12 +1,10 @@
import cgi
import logging
import re
-import types
#from openerp.tools.safe_eval import safe_eval as eval
import xml # FIXME use lxml
-import xml.dom.minidom
import traceback
from openerp.osv import osv, orm
@@ -171,7 +169,7 @@ class QWebXml(object):
t_att = {}
for (an, av) in e.attributes.items():
an = str(an)
- if isinstance(av, types.UnicodeType):
+ if isinstance(av, unicode):
av = av.encode("utf8")
else:
av = av.nodeValue.encode("utf8")
@@ -273,7 +271,7 @@ class QWebXml(object):
var = t_att.get('as', expr).replace('.', '_')
d = QWebContext(v.copy(), self.undefined_handler)
size = -1
- if isinstance(enum, (types.ListType, types.TupleType)):
+ if isinstance(enum, (list, tuple)):
size = len(enum)
elif hasattr(enum, 'count'):
size = enum.count()
@@ -294,7 +292,7 @@ class QWebXml(object):
d["%s_parity" % var] = 'even'
if 'as' in t_att:
d[var] = i
- elif isinstance(i, types.DictType):
+ elif isinstance(i, dict):
d.update(i)
ru.append(self.render_element(e, t_att, g_att, d))
index += 1
@@ -327,35 +325,58 @@ class QWebXml(object):
def render_tag_field(self, e, t_att, g_att, v):
""" eg: +1 555 555 8069"""
+ node_name = e.nodeName
+ assert node_name not in ("table", "tbody", "thead", "tfoot", "tr", "td"),\
+ "RTE widgets do not work correctly on %r elements" % node_name
record, field = t_att["field"].rsplit('.', 1)
record = self.eval_object(record, v)
- inner = ""
- field_type = record._model._all_columns.get(field).column._type
+ inner = None
+ field_type = record._model._all_columns[field].column._type
try:
if field_type == 'many2one':
field_data = record.read([field])[0].get(field)
- inner = field_data and field_data[1] or ""
+ inner = field_data and field_data[1]
else:
- inner = getattr(record, field) or ""
- if isinstance(inner, types.UnicodeType):
+ inner = getattr(record, field)
+
+ if isinstance(inner, unicode):
inner = inner.encode("utf8")
- if field_type != 'html':
- cgi.escape(str(inner))
- if e.tagName != 't':
- g_att += ''.join(
- ' %s="%s"' % (name, cgi.escape(str(value), True))
- for name, value in [
- ('data-oe-model', record._model._name),
- ('data-oe-id', str(record.id)),
- ('data-oe-field', field),
- ('data-oe-type', field_type),
- ]
- )
+
+ if node_name == 't':
+ e.nodeName = DEFAULT_TAG_BY_TYPE[field_type]
+
+ g_att += ''.join(
+ ' %s="%s"' % (name, cgi.escape(str(value), True))
+ for name, value in [
+ ('data-oe-model', record._model._name),
+ ('data-oe-id', str(record.id)),
+ ('data-oe-field', field),
+ ('data-oe-type', field_type),
+ ('data-oe-expression', t_att['field']),
+ ]
+ )
except AttributeError:
_logger.warning("t-field no field %s for model %s", field, record._model._name)
- return self.render_element(e, t_att, g_att, v, str(inner))
+ return self.render_element(e, t_att, g_att, v, str(inner or ""))
+
+# If a t-field is set on a element, by default only its text will be
+# rendered losing the information that it's a tag and completely breaking
+# edition => replace by some default tag depending on field type
+DEFAULT_TAG_BY_TYPE = {
+ 'integer': 'span',
+ 'float': 'span',
+ 'char': 'span',
+ 'date': 'span',
+ 'datetime': 'span',
+ 'time': 'span',
+ 'many2one': 'span',
+
+ 'text': 'p',
+
+ 'html': 'div',
+}
# leave this, al.