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.