[IMP] modules, ir.ui.view: improve view validation + avoid validation errors during updates

As of 7.0, RNG validation is not possible for form views
that have a version attribute equal to "7.0", due to the
allowed usage of HTML syntax mixed with the regular OpenERP
view syntax. RNG validation is still enabled for regular
form views (@version missing or less than "7.0"), and for
all other views types.
Validation of 7.0 form views should be improved with the
addition of an assertion-based schema, still to be done.
 
The above is also complemented with an explicit call to fields_view_get()
during view installation, in order to immediately verify
that the updated view hierarchy does not cause any
issue when loaded along with its related views (i.e
parent and siblings, for inheriting views).
In addition to that, fields_view_get() will now only
consider loading views that belong to modules that have
already been loaded. This avoids a lot of validation errors
during a module update operation, which runs on top of
an existing database with all previous views visible,
even those whose module is not loaded yet.

bzr revid: odo@openerp.com-20120611122758-qcw9xdhupl24busq
This commit is contained in:
Olivier Dony 2012-06-11 14:27:58 +02:00
parent 5fd5165c20
commit 2126f83a35
3 changed files with 66 additions and 39 deletions

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

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

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)