[MERGE] upstream
bzr revid: fme@openerp.com-20140120161129-elnp1afn8ognyc6z
This commit is contained in:
commit
05e2942121
|
@ -18,7 +18,6 @@
|
|||
<field name="street">1725 Slough Ave.</field>
|
||||
<field name="city">Scranton</field>
|
||||
<field name="zip">18540</field>
|
||||
<field name="country_id" ref="us"/>
|
||||
<field name="phone">+1 555 123 8069</field>
|
||||
<field name="email">info@yourcompany.example.com</field>
|
||||
<field name="website">www.example.com</field>
|
||||
|
|
|
@ -99,7 +99,7 @@ class ir_http(osv.AbstractModel):
|
|||
|
||||
# check authentication level
|
||||
try:
|
||||
auth_method = self._authenticate(getattr(func, "auth", None))
|
||||
auth_method = self._authenticate(func.routing["auth"])
|
||||
except Exception:
|
||||
# force a Forbidden exception with the original traceback
|
||||
return self._handle_exception(
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'),
|
||||
|
@ -183,11 +184,18 @@ class view(osv.osv):
|
|||
# TODO: should be doable in a read and a write
|
||||
for view_ in self.browse(cr, uid, ids, context=context):
|
||||
if view_.model_data_id:
|
||||
view_.model_data_id.write({'noupdate': True})
|
||||
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,30 +699,32 @@ 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
|
||||
|
||||
def clear_cache(self):
|
||||
self.read_template.clear_cache(self)
|
||||
|
||||
def distribute_branding(self, e, branding=None, parent_xpath='',
|
||||
index_map=misc.ConstantMapping(1)):
|
||||
if e.get('t-ignore') or e.tag == 'head':
|
||||
|
@ -768,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']:
|
||||
|
@ -788,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=[]
|
||||
|
@ -866,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.
|
||||
|
@ -889,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:
|
||||
|
|
|
@ -600,6 +600,7 @@ class users_implied(osv.osv):
|
|||
if groups:
|
||||
# delegate addition of groups to add implied groups
|
||||
self.write(cr, uid, [user_id], {'groups_id': groups}, context)
|
||||
self.pool['ir.ui.view'].clear_cache()
|
||||
return user_id
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
|
@ -612,6 +613,7 @@ class users_implied(osv.osv):
|
|||
gs = set(concat([g.trans_implied_ids for g in user.groups_id]))
|
||||
vals = {'groups_id': [(4, g.id) for g in gs]}
|
||||
super(users_implied, self).write(cr, uid, [user.id], vals, context)
|
||||
self.pool['ir.ui.view'].clear_cache()
|
||||
return res
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
|
|
@ -168,7 +168,7 @@ class WebRequest(object):
|
|||
if not k.startswith("_ignored_"))
|
||||
|
||||
self.func = func
|
||||
self.func_request_type = func.exposed
|
||||
self.func_request_type = func.routing['type']
|
||||
self.func_arguments = arguments
|
||||
self.auth_method = auth
|
||||
|
||||
|
@ -207,7 +207,7 @@ class WebRequest(object):
|
|||
warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
|
||||
yield (self.registry, self.cr)
|
||||
|
||||
def route(route, type="http", auth="user", methods=None):
|
||||
def route(route=None, **kw):
|
||||
"""
|
||||
Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
|
||||
of ``Controller``.
|
||||
|
@ -226,16 +226,16 @@ def route(route, type="http", auth="user", methods=None):
|
|||
configuration indicating the current database nor the current user.
|
||||
:param methods: A sequence of http methods this route applies to. If not specified, all methods are allowed.
|
||||
"""
|
||||
assert type in ["http", "json"]
|
||||
routing = kw.copy()
|
||||
assert not 'type' in routing or routing['type'] in ("http", "json")
|
||||
def decorator(f):
|
||||
if isinstance(route, list):
|
||||
f.routes = route
|
||||
else:
|
||||
f.routes = [route]
|
||||
f.methods = methods
|
||||
f.exposed = type
|
||||
if getattr(f, "auth", None) is None:
|
||||
f.auth = auth
|
||||
if route:
|
||||
if isinstance(route, list):
|
||||
routes = route
|
||||
else:
|
||||
routes = [route]
|
||||
routing['routes'] = routes
|
||||
f.routing = routing
|
||||
return f
|
||||
return decorator
|
||||
|
||||
|
@ -388,11 +388,10 @@ def jsonrequest(f):
|
|||
|
||||
Use the ``route()`` decorator instead.
|
||||
"""
|
||||
f.combine = True
|
||||
base = f.__name__.lstrip('/')
|
||||
if f.__name__ == "index":
|
||||
base = ""
|
||||
return route([base, base + "/<path:_ignored_path>"], type="json", auth="user")(f)
|
||||
return route([base, base + "/<path:_ignored_path>"], type="json", auth="user", combine=True)(f)
|
||||
|
||||
class HttpRequest(WebRequest):
|
||||
""" Regular GET/POST request
|
||||
|
@ -444,11 +443,10 @@ def httprequest(f):
|
|||
|
||||
Use the ``route()`` decorator instead.
|
||||
"""
|
||||
f.combine = True
|
||||
base = f.__name__.lstrip('/')
|
||||
if f.__name__ == "index":
|
||||
base = ""
|
||||
return route([base, base + "/<path:_ignored_path>"], type="http", auth="user")(f)
|
||||
return route([base, base + "/<path:_ignored_path>"], type="http", auth="user", combine=True)(f)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Controller and route registration
|
||||
|
@ -501,13 +499,22 @@ def routing_map(modules, nodb_only, converters=None):
|
|||
o = cls()
|
||||
members = inspect.getmembers(o)
|
||||
for mk, mv in members:
|
||||
if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and (not nodb_only or nodb_only == (mv.auth == "none")):
|
||||
for url in mv.routes:
|
||||
if getattr(mv, "combine", False):
|
||||
url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
|
||||
if url.endswith("/") and len(url) > 1:
|
||||
url = url[: -1]
|
||||
routing_map.add(werkzeug.routing.Rule(url, endpoint=mv, methods=mv.methods))
|
||||
if inspect.ismethod(mv) and hasattr(mv, 'routing'):
|
||||
routing = dict(type='http', auth='user', methods=None)
|
||||
for claz in reversed(mv.im_class.mro()):
|
||||
fn = getattr(claz, mv.func_name, None)
|
||||
if fn and hasattr(fn, 'routing'):
|
||||
routing.update(fn.routing)
|
||||
mv.routing.update(routing)
|
||||
assert 'routes' in mv.routing
|
||||
if not nodb_only or nodb_only == (mv.routing['auth'] == "none"):
|
||||
for url in mv.routing['routes']:
|
||||
if mv.routing.get("combine", False):
|
||||
# deprecated
|
||||
url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
|
||||
if url.endswith("/") and len(url) > 1:
|
||||
url = url[: -1]
|
||||
routing_map.add(werkzeug.routing.Rule(url, endpoint=mv, methods=mv.routing['methods']))
|
||||
return routing_map
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
|
|
@ -106,7 +106,7 @@ class WorkflowInstance(object):
|
|||
for cur_instance_id, cur_model_name, cur_record_id in cr.fetchall():
|
||||
cur_record = Record(cur_model_name, cur_record_id)
|
||||
for act_name in act_names:
|
||||
WorkflowInstance(self.session, cur_record, {'id':cur_instance_id}).validate('subflow.{}'.format(act_name[0]))
|
||||
WorkflowInstance(self.session, cur_record, {'id':cur_instance_id}).validate('subflow.%s' % act_name[0])
|
||||
|
||||
return ok
|
||||
|
||||
|
|
Loading…
Reference in New Issue