[MERGE] upstream
bzr revid: fme@openerp.com-20130925085305-wbmdcm75i6o1qm30
This commit is contained in:
commit
e01f97e8f7
|
@ -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
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue