QWeb cleanups

bzr revid: jojo_le_bricolo-20140119174437-kjrr9p1smpdxhd3u
This commit is contained in:
Jojo le bricolo 2014-01-19 18:44:37 +01:00
parent bd444b8a5e
commit 6a4ea0f9a4
2 changed files with 79 additions and 76 deletions

View File

@ -20,6 +20,9 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
#--------------------------------------------------------------------
# QWeb template engine
#--------------------------------------------------------------------
class QWebException(Exception):
def __init__(self, message, template=None, node=None, attribute=None):
@ -28,7 +31,6 @@ class QWebException(Exception):
self.node = node
self.attribute = attribute
class QWebContext(dict):
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
self.cr = cr
@ -59,29 +61,30 @@ class QWebContext(dict):
class QWeb(orm.AbstractModel):
"""QWeb Xml templating engine
The templating engine use a very simple syntax, "magic" xml attributes, to
produce any kind of texutal output (even non-xml).
The templating engine use a very simple syntax based "magic" xml
attributes, to produce textual output (even non-xml).
QWebXml:
the template engine core implements the basic magic attributes:
The core magic attributes are:
t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
flow attributes:
t-if t-foreach t-call
output attributes:
t-att t-raw t-esc t-trim
- loader: function that return a template
assignation attribute:
t-set
QWeb rendering can be used for many tasks. As a result, customizations
made by one task context (to either the main qweb rendering or to specific
fields rendering) could break other tasks.
To avoid that, ``ir.qweb`` was consciously made inheritable and the "root"
of an object hierarchy. If you need extensions or alterations which could
be incompatible with other subsystems, you should create a local object
inheriting from ``ir.qweb`` and customize that.
QWeb can be extended like any OpenERP model and new attributes can be
added.
If you need to customize t-fields rendering, subclass the ir.qweb.field
model (and its sub-models) then override :meth:`~.get_converter_for` to
fetch the right field converters for your qweb model.
Beware that if you need extensions or alterations which could be
incompatible with other subsystems, you should create a local object
inheriting from ``ir.qweb`` and customize that.
"""
_name = 'ir.qweb'
@ -121,7 +124,7 @@ class QWeb(orm.AbstractModel):
def register_tag(self, tag, func):
self._render_tag[tag] = func
def load_document(self, x, into, context):
def load_document(self, x, qcontext):
"""
Loads an XML document and installs any contained template in the engine
"""
@ -134,18 +137,20 @@ class QWeb(orm.AbstractModel):
for n in dom.documentElement.childNodes:
if n.nodeType == self.node.ELEMENT_NODE and n.getAttribute('t-name'):
self.add_template(into, str(n.getAttribute("t-name")), n, context)
name = str(n.getAttribute("t-name"))
qcontext.templates[name] = n
def add_template(self, into, name, node, context):
into[name] = node
def get_template(self, name, qcontext):
if qcontext.loader and name not in qcontext.templates:
xml_doc = qcontext.loader(name)
self.load_document(xml_doc, qcontext=qcontext)
def get_template(self, name, context):
if context.loader and name not in context.templates:
xml_doc = context.loader(name)
self.load_document(xml_doc, into=context.templates, context=context)
if name in qcontext.templates:
return qcontext.templates[name]
if name in context.templates:
return context.templates[name]
import pdb
pdb.set_trace()
e = KeyError('qweb: template "%s" not found' % name)
setattr(e, 'qweb_template', name)
@ -191,32 +196,19 @@ class QWeb(orm.AbstractModel):
else:
return 0
@openerp.tools.ormcache()
def get_template_xmlid(self, cr, uid, id):
imd = self.pool['ir.model.data']
domain = [('model', '=', 'ir.ui.view'), ('res_id', '=', id)]
xmlid = imd.search_read(cr, uid, domain, ['module', 'name'])[0]
return '%s.%s' % (xmlid['module'], xmlid['name'])
def render(self, cr, uid, id_or_xml_id, v=None, loader=None, context=None):
if isinstance(id_or_xml_id, list):
id_or_xml_id = id_or_xml_id[0]
tname = id_or_xml_id
if isinstance(id_or_xml_id, (int, long)):
tname = self.get_template_xmlid(cr, uid, tname)
if v is None:
v = {}
if not isinstance(v, QWebContext):
v = QWebContext(cr, uid, v, loader=loader, context=context)
v['__template__'] = tname
v['__template__'] = id_or_xml_id
stack = v.get('__stack__', [])
if stack:
v['__caller__'] = stack[-1]
stack.append(tname)
stack.append(id_or_xml_id)
v['__stack__'] = stack
v['xmlid'] = str(stack[0]) # Temporary fix
return self.render_node(self.get_template(tname, v), v)
return self.render_node(self.get_template(id_or_xml_id, v), v)
def render_node(self, e, v):
r = ""
@ -314,10 +306,13 @@ class QWeb(orm.AbstractModel):
def render_att_href(self, e, an, av, v):
return self.url_for(e, an, av, v)
def render_att_src(self, e, an, av, v):
return self.url_for(e, an, av, v)
def render_att_action(self, e, an, av, v):
return self.url_for(e, an, av, v)
def url_for(self, e, an, av, v):
if 'url_for' not in v:
raise KeyError("qweb: no 'url_for' found in context")
@ -384,7 +379,7 @@ class QWeb(orm.AbstractModel):
return ""
def render_tag_call(self, e, t_att, g_att, v):
d = v if 'import' in t_att else v.copy()
d = v.copy()
d[0] = self.render_element(e, t_att, g_att, d)
return self.render(None, None, self.eval_format(t_att["call"], d), d)
@ -423,6 +418,9 @@ class QWeb(orm.AbstractModel):
return self.pool.get('ir.qweb.field.' + field_type,
self.pool['ir.qweb.field'])
#--------------------------------------------------------------------
# QWeb Fields converters
#--------------------------------------------------------------------
class FieldConverter(osv.AbstractModel):
""" Used to convert a t-field specification into an output HTML field.

View File

@ -40,6 +40,8 @@ from openerp.osv.orm import browse_record, browse_record_list
_logger = logging.getLogger(__name__)
MOVABLE_BRANDING = ['data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-xpath']
class view_custom(osv.osv):
_name = 'ir.ui.view.custom'
_order = 'create_date desc' # search(limit=1) should return the last customization
@ -71,7 +73,6 @@ class view(osv.osv):
'type': fields.selection([
('tree','Tree'),
('form','Form'),
('mdx','mdx'),
('graph', 'Graph'),
('calendar', 'Calendar'),
('diagram','Diagram'),
@ -186,8 +187,15 @@ class view(osv.osv):
self.pool.get('ir.model.data').write(cr, openerp.SUPERUSER_ID, view_.model_data_id.id, {'noupdate': True})
return ret
# default view selection
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({
'model_ids': [],
})
return super(view, self).copy(cr, uid, id, default, context=context)
# default view selection
def default_view(self, cr, uid, model, view_type, context=None):
""" Fetches the default view for the provided (model, view_type) pair:
view with no parent (inherit_id=Fase) with the lowest priority.
@ -207,8 +215,9 @@ class view(osv.osv):
return False
return ids[0]
# inheritance
#------------------------------------------------------
# Inheritance mecanism
#------------------------------------------------------
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
"""Retrieves the architecture of views that inherit from the given view, from the sets of
views that should currently be used in the system. During the module upgrade phase it
@ -422,8 +431,13 @@ class view(osv.osv):
return dict(view, arch=etree.tostring(arch, encoding='utf-8'))
# postprocessing: groups, modifiers, ...
#------------------------------------------------------
# Postprocessing: translation, groups and modifiers
#------------------------------------------------------
# TODO:
# - split postprocess so that it can be used instead of translate_qweb
# - remove group processing from ir_qweb
#------------------------------------------------------
def postprocess(self, cr, user, model, node, view_id, in_tree_view, model_fields, context=None):
"""Return the description of the fields in the node.
@ -685,26 +699,25 @@ class view(osv.osv):
raise orm.except_orm('View error', msg)
return arch, fields
# view used as templates
#------------------------------------------------------
# QWeb template views
#------------------------------------------------------
@tools.ormcache_context(accepted_keys=('lang','inherit_branding', 'editable', 'translatable'))
def read_template(self, cr, uid, id_, context=None):
try:
id_ = int(id_)
except ValueError:
if '.' not in id_:
raise ValueError('Invalid id: %r' % (id_,))
IMD = self.pool['ir.model.data']
m, _, n = id_.partition('.')
_, id_ = IMD.get_object_reference(cr, uid, m, n)
def read_template(self, cr, uid, xml_id, context=None):
if '.' not in xml_id:
raise ValueError('Invalid template id: %r' % (xml_id,))
arch = self.read_combined(cr, uid, id_, fields=['arch'], context=context)['arch']
m, n = xml_id.split('.', 1)
_, view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, m, n)
arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch']
arch_tree = etree.fromstring(arch)
if 'lang' in context:
arch_tree = self.translate_qweb(cr, uid, id_, arch_tree, context['lang'], context)
arch_tree = self.translate_qweb(cr, uid, view_id, arch_tree, context['lang'], context)
self.distribute_branding(arch_tree)
root = etree.Element('tpl')
root = etree.Element('templates')
root.append(arch_tree)
arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
return arch
@ -771,8 +784,6 @@ class view(osv.osv):
text = h.unescape(text.strip())
if len(text) < 2 or (text.startswith('<!') and text.endswith('>')):
return None
# if text == 'Our Events':
# from pudb import set_trace;set_trace() ############################## Breakpoint ##############################
return Translations._get_source(cr, uid, 'website', 'view', lang, text, id_)
if arch.tag not in ['script']:
@ -791,16 +802,18 @@ class view(osv.osv):
self.translate_qweb(cr, uid, id_, node, lang, context)
return arch
def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None):
def render(self, cr, uid, xml_id, values=None, engine='ir.qweb', context=None):
if not context:
context = {}
def loader(name):
return self.read_template(cr, uid, name, context=context)
return self.pool[engine].render(cr, uid, id_or_xml_id, values, loader=loader, context=context)
return self.pool[engine].render(cr, uid, xml_id, values, loader=loader, context=context)
# maybe used to print the workflow ?
#------------------------------------------------------
# Misc
#------------------------------------------------------
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None):
nodes=[]
@ -869,14 +882,6 @@ class view(osv.osv):
'blank_nodes': blank_nodes,
'node_parent_field': _Model_Field,}
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({
'model_ids': [],
})
return super(view, self).copy(cr, uid, id, default, context=context)
def _validate_custom_views(self, cr, uid, model):
"""Validate architecture of custom views (= without xml id) for a given model.
This method is called at the end of registry update.
@ -892,4 +897,4 @@ class view(osv.osv):
ids = map(itemgetter(0), cr.fetchall())
return self._check_xml(cr, uid, ids)
MOVABLE_BRANDING = ['data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-xpath']
# vim:et: