[MERGE] upstream

bzr revid: fme@openerp.com-20130925085305-wbmdcm75i6o1qm30
This commit is contained in:
Fabien Meghazi 2013-09-25 10:53:05 +02:00
commit e01f97e8f7
7 changed files with 175 additions and 92 deletions

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
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 = '<?xml version="1.0" encoding="utf-8"?><tpl>%s</tpl>' % (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'

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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 <module>.<id>
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('<field name="name">%s</field>' % name))
record.append(etree.fromstring('<field name="type">qweb</field>'))
record.append(etree.fromstring('<field name="arch" type="xml"/>'))
record[-1].append(el)
for key in ('inherit_id','inherit_option_id'):
if el.get(key):
record.append(etree.fromstring('<field name="%s" ref="%s"/>' % (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):

View File

@ -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 <span> ... <a href="#">read more</a></span> 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')

View File

@ -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:

View File

@ -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: <span t-record="browse_record(res.partner, 1)" t-field="phone">+1 555 555 8069</span>"""
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 <t> element, by default only its text will be
# rendered losing the information that it's a tag and completely breaking
# edition => replace <t> 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.