[MERGE] Repair RNG validation, enable rendering check, fix validation in update mode

- RNG validation restored for non 7.0-style views
- RNG schema document now cached for faster validation
- Extra view validation now performed by forcing a rendering
  of each created/updated view via fields_view_get, including
  inherited views
- Fixed fields_view_get when called during module update, i.e.
  while modules are only partially loaded - it will now only
  consider views belonging to already loaded modules only

bzr revid: odo@openerp.com-20120611203431-syzqe4n2jbrqv17u
This commit is contained in:
Olivier Dony
2012-06-11 22:34:31 +02:00
4 changed files with 83 additions and 49 deletions
+61 -38
View File
@@ -73,42 +73,54 @@ class view(osv.osv):
}
_order = "priority,name"
def _check_xml(self, cr, uid, ids, context=None):
def root_view(record):
if not record.inherit_id:
return record
else:
return root_view(record.inherit_id)
def call_view(record):
try:
root = root_view(record)
# Temporarily commented, waiting fixes; it breaks most updates because
# when you update modules, all views are in the database, but only
# fields of loaded objects are on the objects. This breaks the
# fields_view_get calls. (field exists in view but not in the object)
#
# self.pool.get(root.model).fields_view_get(cr, uid, view_id=root.id, view_type=root.type, context=context)
# Holds the RNG schema
_relaxng_validator = None
return True
except:
_logger.exception("Can't obtain view description for model: %s (view id: %s).", record.model, record.id)
return False
for view in self.browse(cr, uid, ids, context):
return call_view(view)
# TODO The following code should be executed (i.e. no early return above).
# TODO The view.rng file can be read only once!
eview = etree.fromstring(view.arch.encode('utf8'))
def _relaxng(self):
if not self._relaxng_validator:
frng = tools.file_open(os.path.join('base','rng','view.rng'))
try:
relaxng_doc = etree.parse(frng)
relaxng = etree.RelaxNG(relaxng_doc)
if not relaxng.validate(eview):
for error in relaxng.error_log:
_logger.error(tools.ustr(error))
return False
self._relaxng_validator = etree.RelaxNG(relaxng_doc)
except Exception:
_logger.exception('Failed to load RelaxNG XML schema for views validation')
finally:
frng.close()
return self._relaxng_validator
def _check_render_view(self, cr, uid, view, context=None):
"""Verify that the given view's hierarchy is valid for rendering, along with all the changes applied by
its inherited views, by rendering it using ``fields_view_get()``.
@param browse_record view: view to validate
@return: True if the view hierarchy was rendered without any error, False if an error occurred.
"""
try:
self.pool.get(view.model).fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context)
return True
except:
_logger.exception("Can't render view %s for model: %s", view.xml_id, view.model)
return False
def _check_xml(self, cr, uid, ids, context=None):
for view in self.browse(cr, uid, ids, context):
# RNG-based validation is not possible anymore with 7.0 forms
# TODO 7.0: provide alternative assertion-based validation!
view_docs = [etree.fromstring(view.arch.encode('utf8'))]
if view_docs[0].tag == 'data':
# A <data> element is a wrapper for multiple root nodes
view_docs = view_docs[0]
validator = self._relaxng()
for view_arch in view_docs:
if (view_arch.get('version') < '7.0') and validator and not validator.validate(view_arch):
for error in validator.error_log:
_logger.error(tools.ustr(error))
return False
# Second sanity check: the view should not break anything upon rendering!
if not self._check_render_view(cr, uid, view, context=context):
return False
return True
_constraints = [
@@ -122,20 +134,31 @@ class view(osv.osv):
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, type, inherit_id)')
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
"""Retrieves the architecture of views that inherit from the given view.
"""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
may happen that a view is present in the database but the fields it relies on are not
fully loaded yet. This method only considers views that belong to modules whose code
is already loaded. Custom views defined directly in the database are loaded only
after the module initialization phase is completely finished.
:param int view_id: id of the view whose inheriting views should be retrieved
:param str model: model identifier of the view's related model (for double-checking)
:rtype: list of tuples
:return: [(view_arch,view_id), ...]
"""
cr.execute("""SELECT
arch, id
FROM
ir_ui_view
WHERE
inherit_id=%s AND model=%s
ORDER BY priority""", (view_id, model))
if self.pool._init:
# Module init currently in progress, only consider views from modules whose code was already loaded
query = """SELECT v.arch, v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
WHERE v.inherit_id=%s AND v.model=%s AND md.module in %s
ORDER BY priority"""
query_params = (view_id, model, tuple(self.pool._init_modules))
else:
# Modules fully loaded, consider all views
query = """SELECT v.arch, v.id FROM ir_ui_view v
WHERE v.inherit_id=%s AND v.model=%s
ORDER BY priority"""
query_params = (view_id, model)
cr.execute(query, query_params)
return cr.fetchall()
def write(self, cr, uid, ids, vals, context=None):
+1 -1
View File
@@ -154,7 +154,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
loaded_modules.append(package.name)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_models(cr, package.name, models)
pool._init_modules.add(package.name)
status['progress'] = float(index) / len(graph)
# Can't put this line out of the loop: ir.module.module will be
+4
View File
@@ -48,6 +48,10 @@ class Registry(object):
self._store_function = {}
self._init = True
self._init_parent = {}
# modules fully loaded (maintained during init phase by `loading` module)
self._init_modules = set()
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
+17 -10
View File
@@ -1977,6 +1977,7 @@ class BaseModel(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
error_msg = error_msg % {'parent_xml_id': view.xml_id}
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, self._name, error_msg))
@@ -2005,16 +2006,18 @@ class BaseModel(object):
if node.get('name') == spec.get('name'):
return node
return None
else:
for node in source.getiterator(spec.tag):
good = True
for attr in spec.attrib:
if attr != 'position' and (not node.get(attr) or node.get(attr) != spec.get(attr)):
good = False
break
if good:
return node
return None
for node in source.getiterator(spec.tag):
if isinstance(node, SKIPPED_ELEMENT_TYPES):
continue
if all(node.get(attr) == spec.get(attr) \
for attr in spec.attrib
if attr not in ('position','version')):
# Version spec should match parent's root element's version
if spec.get('version') and spec.get('version') != source.get('version'):
return None
return node
return None
def apply_inheritance_specs(source, specs_arch, inherit_id=None):
""" Apply an inheriting view.
@@ -2080,7 +2083,11 @@ class BaseModel(object):
if attr != 'position'
])
tag = "<%s%s>" % (spec.tag, attrs)
if spec.get('version') and spec.get('version') != source.get('version'):
raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
(tag, spec.get('version'), source.get('version')), inherit_id)
raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
return source
def apply_view_inheritance(cr, user, source, inherit_id):